How to create validated TextField entensions in JavaFX - javafx

I want to create a JavaFX fxml based dialog, where the user can enter a bunch of integer and double values. I created the dialog in SceneBuilder using for each of the values a dedicated TextField.
Intentionally I am not using Binding between the TextFields and the model. In order to NOT add a ChangeListener or set a TextFormatter to each of these TextFields in the controller again and again, I created a dedicated IntegerTextField and DoubleTextField class, e.g.
public class IntegerTextField extends TextField {
protected static Pattern decimalPattern = Pattern.compile("^-?\\d+$"); // Double ("-?\\d*(\\.\\d{0,1})?");
public IntegerTextField() {
super();
setTextFormatter(new TextFormatter<>(c -> (decimalPattern.matcher(c.getControlNewText()).matches()) ? c : null ));
}
public int getInt() {
try {
return Integer.parseInt(getText());
}
catch (NumberFormatException e) {
return 0;
}
}
}
and in the Controller class I replaced the previous
#FXML private TextField setsTextField;
with
#FXML private IntegerTextField setsTextField;
When I got the
javafx.fxml.LoadException:...Can not set util.IntegerTextField field ctrl.ExerciseEditorCtrl.setsTextField to javafx.scene.control.TextField
I realized that this implicit downcasting doesn't work.
Is there a way to do this properly with fxml or is it neccessary to have the dialog setup in a java class when using IntegerTextField?

Related

How to use setOnAction event on javafx

I would like to validate if a textfield is empty or not using javafx.
I am confused of event handlers. I want to confirm :
- whether there are many ways to use setOnAction :
submit.setOnAction((new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
System.out.println("Hello World");
}
}));
or
submit.setOnAction(e -> handle(e));
what is the difference between these two choices?
whether the second choice can not use the ActionEvent
submit.setOnAction(e -> handle());
but then what is the purpose of defining e?
I would like to validate textfields in my application.
public class AppGUI extends Application{
public static void main(String[] args)
{
launch();
}
public void start(Stage topView)
{
createUI(topView);
}
private void createUI(Stage topView)
{
TextField name = TextField();
Button submit = new Button("Submit");
submit.setOnAction(e -> validate());
}
private boolean validate()
{
// if textfield is empty, return false. else, return true.
}
I am lost here. Is it okay if the e in setOnAction is not used in validate? How do I pass the value of textfield to validate()? is making the textfields private variables the only way? (because I have so many text fields I wonder if its a good option).
in createUI method, how do i say if validate() returns false, show error message and if true, do something else?
Thank you and sorry for bothering
what is the difference between these two choices?
In second option lambdas are used (appeared since Java 8)
but then what is the purpose of defining e?
For a button your method have a signature like this setOnAction(EventHandler<ActionEvent> handler) You should see EventHandler tutorials and an ActionEvent javadoc. For instance, from e you can get the object on which the Event initially occurred this way e.getSource()
It is ok if you don't use e in validate.
To pass the value of textfield your method should have signature like this
boolean validate(String text);
Code example:
private void createUI(Stage topView){
TextField name = TextField();
Button submit = new Button("Submit");
submit.setOnAction(e -> {
boolean validated = validate(name.getText());
if(validated) System.out.println("validated");
}
}
private boolean validate(String text){
return text != null && !text.isEmpty();
}

javafx: How to bind disable button with dynamic created checkboxes?

I want to bind the disable of a button with dynamically created checkboxes. The Button should be enabled if a checkbox is selected.
This is my code
public class DietTabPageController {
#FXML
private FlowPane parent;
#FXML
private Button okButton;
private ObservableList<CheckBox> checkBoxes=FXCollections.observableArrayList();
#FXML
private void initialize() {
ObservableList<Diet> diets = DietDAO.getDiets();
diets.forEach(diet -> checkBoxes.add(new CheckBox(diet.getName())));
//checkboxes added in parent Flowpane
parent.getChildren().addAll(checkBoxes);
}
}
Any suggestions? Thanks
You can use JavaFX's really nice Bindings-class!
Try this:
okButton.disableProperty().bind(
Bindings.createBooleanBinding(
()->!checkBoxes.stream().anyMatch(CheckBox::isSelected),
checkBoxes.stream().map(x->x.selectedProperty()).toArray(Observable[]::new)
)
);
This creates a new Binding, which will listen on every checkbox and then call the given function to calculate the value of your property.
Additional reading here: Bindings
Regarding your comment:
I don't know how much you can edit your Diet class, but if you can, there is a very simple way to display your checkboxes and add the button-binding. Take a look at the following sample:
ListView<Diet> dietsView = new ListView<>(diets);
dietsView.setCellFactory(CheckBoxListCell.forListView(diet ->
diet.selectedProperty()));
btn.disableProperty().bind(
Bindings.createBooleanBinding(
() -> !diets.stream().anyMatch(diet->diet.isSelected()),
diets.stream().map(x->x.selectedProperty())
.toArray(Observable[]::new)
)
);
add this to Diet class:
private final BooleanProperty selected = new SimpleBooleanProperty();
public final BooleanProperty selectedProperty() {
return this.selected;
}
public final boolean isSelected() {
return this.selectedProperty().get();
}
public final void setSelected(final boolean on) {
this.selectedProperty().set(on);
}
You need to add listeners to all the selected properties of the CheckBoxes. Every time one of the property changes, modify the Button's disable property, if necessary. BTW: Making checkBoxes observable doesn't seem necessary:
private List<CheckBox> checkBoxes;
#FXML
private void initialize() {
ObservableList<Diet> diets = DietDAO.getDiets();
checkBoxes = new ArrayList<>(diets.size());
ChangeListener<Boolean> listener = (o, oldValue, newValue) -> {
if (newValue) {
// activate button since at least one CheckBox is selected
okButton.setDisable(false);
} else {
// disable button, if the last CheckBox was unselected
for (CheckBox cb : checkBoxes) {
if (cb.isSelected()) {
return; // don't do anything, if there still is a selected CheckBox
}
}
okButton.setDisable(true);
}
};
for (Diet diet : diets) {
CheckBox cb = new CheckBox(diet.getName());
cb.selectedProperty().addListener(listener);
checkBoxes.add(cb);
}
//checkboxes added in parent Flowpane
parent.getChildren().addAll(checkBoxes);
}

How do I automatically trigger enter key in Javafx

Nowadays I am working on raspberry pi and I write some programs in java , javafx platforms.I just would like to inform you that I am simply beginner on javafx.
According to that I just would like to trigger ENTER key after changing my textfield.Working principle of my program is like this;
1)I have created one masterform fxml and it is directing all other pages with one textfield.
2)I created main method that let me to use keyboard to enter some specific String values to assign them to textfield for page alteration.
3)I have a bridge java page, it includes global variables to use everywhere in project.So Firstly I set value from keyboard to these global variables.These global variables are created as stringproperty for adding actionlistener for any change.
4)Then I set these global variables to textfield.
5)Textfield indicates relevant values from keyboard.But Unfortunately I can not forward the pages without pressing to enter key.In this case ı would like to trigger this textfield.But unfortunately ı have no idea how to trigger texfield without pressing enter key.Therefore I decided to make auto trigger to enter key for this textfield.
I simply used robot method;
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_ENTER);
But it didn't work.Because After I set the global variable to textfield for first time.It does not define the value of the textfield is changed.It determines after pressing the enter key.
So how can I trigger this textfield after getting value of my global variables.I would like to pass how to set pages, I will show you how my program works.
Example of my code is;
Main method
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
for (String strBarcode = scanner.nextLine(); !strBarcode.isEmpty();
strBarcode = scanner.nextLine()) {
if (strBarcode.equals("distribution")){
Global.G_MOD.set("distribution");
System.out.println(Global.G_MOD.get());
}
}}
GlobalVariables.java(bridge page)
public class Global{
public static StringProperty G_MOD = new SimpleStringProperty("");
}
My MasterController Page for javafx
public class masterformController implements Initializable {
#FXML
public TextField tbxBarcode;
#FXML
void onchangetbxBarcode(ActionEvent event) {
if(Global.G_MOD.get().equals("distribution")){
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/puttolightfx/fxml/page1.fxml"));
Parent rootpage1 = (Parent)loader.load();
pnPages.getChildren().clear();
pnPages.getChildren().add(rootpage1);
} catch (IOException ex) {
Logger.getLogger(masterformController.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
Global.G_MOD.addListener(new ChangeListener(){
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
String Newvalue = (String)newValue;
tbxBarcode.setText(Global.G_MOD.get());}
});
}
}
So Everything is working, just I have to trigger textfield when the global value : Global.G_MOD is indicated on texfield.Then it will pass to another page according to global value of Global.G_MOD : "distribution".
SOLUTION(SOLVED):
I solved my problem using thread on listener of the textfield.I gave up to trigger enter key automatically and focused on textfield change.
I simply decided to use thread to change .fxml pages in textfield listener.
Platform.runLater(new Runnable() {
#Override
public void run() {
//if you change the UI, do it here !
}
});
EDITED CODE :
tbxBarcode.textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
String Newvalue=(String)newValue;
System.out.println(tbxBarcode.getText());
Platform.runLater(new Runnable() {
#Override
public void run() {
if(Global.G_MOD.get().equals("distribution")){
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/puttolightfx/fxml/page1.fxml"));
Parent rootpage1 = (Parent)loader.load();
pnPages.getChildren().clear();
pnPages.getChildren().add(rootpage1);
} catch (IOException ex) {
Logger.getLogger(masterformController.class.getName()).log(Level.SEVERE, null, ex);
}
}
// }
}
});
});
Try using
textField.fireEvent(new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.ENTER, true, true, true, true));
According to the docs
public KeyEvent(EventType<KeyEvent> eventType,
String character,
String text,
KeyCode code,
boolean shiftDown,
boolean controlDown,
boolean altDown,
boolean metaDown)
Constructs new KeyEvent event with null source and target and KeyCode object directly specified.
Parameters:
eventType - The type of the event.
character - The character or sequence of characters associated with the event
text - A String describing the key code
code - The integer key code
shiftDown - true if shift modifier was pressed.
controlDown - true if control modifier was pressed.
altDown - true if alt modifier was pressed.
metaDown - true if meta modifier was pressed.
Since:
JavaFX 8.0
You can refer https://docs.oracle.com/javase/8/javafx/api/javafx/scene/input/KeyEvent.html
Edit 1
You need to identify the moment when Enter key event must be triggered.
For example:
If your textfield allows a limited number of characters, then you can add the above mentioned code in the following way:
txtField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (newValue.length()>30) {
txtField.setText(oldValue);
txtField.fireEvent(new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.ENTER, true, true, true, true));
}
}
});
This is just an example. It can fire your event multiple times, so you need to write the code to fire the event just once.

Javafx pass parameter and values from one controller to another

I am new to JavaFx and hence I cannot find a solution to solve my problem
Suppose I have following application structure :
- views
- first.fxml -> this has a button called btnSend and a textfield called txtEnter
- second.fxml -> this has a textarea called txtView
- Controller
- FirstController -> controller for First
- SecondController -> controller for second
- Modal
- AppModal -> here I have a getter and a setter method ,
as getText() and setText(String text)
- App
- Main.java -> This one used FXMLLoader to load first.fxml and second.fxml together.
What is the optimal/best way to display the text in SecondController passing it from FirstController. I mean, I enter a text in txtEnter and press the button btnSend and after pressing the button I want the text to be displayed in txtView which is using another controller.
I have read a lot about the observers pattern and JavaFX properties can be used to solve this, but unfortunately I am unable to implement a working solution.
I would be humbly thankful if you experts can help me in this. I know its not correct but can anyone please give me a working solution for the above project structure.
Thanks in advance.
Use an observable StringProperty in the model:
public class AppModel {
private final StringProperty text = new SimpleStringProperty();
public StringProperty textProperty() {
return text ;
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
}
Make your controllers have access to the model:
public class FirstController {
private final AppModel model ;
#FXML
private TextField textEnter ;
public FirstController(AppModel model) {
this.model = model ;
}
// action event handler for button:
#FXML
private void sendText() {
model.setText(textEnter.getText());
}
}
and
public class SecondController {
private final AppModel model ;
#FXML
private TextArea txtView ;
public SecondController(AppModel model) {
this.model = model ;
}
public void initialize() {
// update text area if text in model changes:
model.textProperty().addListener((obs, oldText, newText) ->
txtView.setText(newText));
}
}
The slightly tricky part now is that the controllers don't have a no-arg constructor, which means the default mechanism for the FXMLLoader to create them won't work. The easiest way is to set them manually. Remove both the <fx:controller> attributes from the FXML files, and then in your Main class do
AppModel model = new AppModel();
FXMLLoader firstLoader = new FXMLLoader(getClass().getResource("first.fxml"));
firstLoader.setController(new FirstController(model));
Parent firstUI = firstLoader.load();
FXMLLoader secondLoader = new FXMLLoader(getClass().getResource("second.fxml"));
secondLoader.setController(new SecondController(model));
Parent secondUI = secondLoader.load();
If you prefer to keep the <fx:controller> attributes in the FXML files, you can use a controllerFactory instead, which essentially instructs the FXMLLoader as to how to create a controller:
AppModel model = new AppModel();
Callback<Class<?>, Object> controllerFactory = type -> {
if (type == FirstController.class) {
return new FirstController(model);
} else if (type == SecondController.class) {
return new SecondController(model);
} else {
try {
return type.newInstance() ; // default behavior - invoke no-arg construtor
} catch (Exception exc) {
System.err.println("Could not create controller for "+type.getName());
throw new RuntimeException(exc);
}
}
};
FXMLLoader firstLoader = new FXMLLoader(getClass().getResource("first.fxml"));
firstLoader.setControllerFactory(controllerFactory);
Parent firstUI = firstLoader.load();
FXMLLoader secondLoader = new FXMLLoader(getClass().getResource("second.fxml"));
secondLoader.setControllerFactory(controllerFactory);
Parent secondUI = secondLoader.load();
You can make the controller factory even more flexible by using (more) reflection; basically you can implement the logic "if the controller type has a constructor taking an AppModel, call that constructor, otherwise call the no-arg constructor".
If you are creating a large application which needs to do a lot of this, then you might consider using afterburner.fx, which is a framework that essentially allows you to inject the model into the controllers using annotations.

Update field on valuechange

I have two fields binded with a fieldgroup. I need to make the second field change when the first field loses focus.
What I have so far:
class MyBean {
private String first;
private String second;
//getters, setters
}
class MasterData extends CustomComponent{
private TextField first;
private TextField second;
//add to layout, other tasks
}
//the calling code
FieldGroup fieldgroup = new FieldGroup(new MyBean());
fieldgroup.bindMemberFields(new MasterData());
((AbstractComponent)fg.getField("first")).setImmediate(true);
fg.getField("first").addValueChangeListener(new ValueChangeListener() {
#Override
public void valueChange(ValueChangeEvent event) {
MyBean bean = fg.getItemDataSource().getBean();
bean.setSecond((String) event.getProperty().getValue());
try {
fg.commit();
} catch (CommitException e) { }
}
});
The value change event is called but the second field never gets updated on the screen. How canI force the fieldgroup to repaint its field?
You might want to take a look at BlurListener.
Also, I think you need to update the value of the TextField "manually". Changing it in the bean might no update the TextField. When you call commit() on the FieldGroup it commits the values in the field to the bean, not the other way around. So in the listener's implementation, it might look something like this:
second.setValue(event.getProperty().getValue());
try {
fg.commit();
} catch (CommitException e) { }

Resources