Shared ChangeListener VS multiple ChangeListeners? [closed] - javafx

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
I have several TextFields that need to perform certain actions when focus is lost, like: trimming text, comparing it to initial one, checking it and if it's valid - saving. Which way would be better to handle it? By creating one ChangeListener with switch statement and sharing it among my Textfields:
ChangeListener<Boolean> focusHandler = new ChangeListener<Boolean>() {
private String initialValue = null;
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean focus) {
ReadOnlyBooleanProperty booleanProperty = (ReadOnlyBooleanProperty) observable;
TextField textField = (TextField)booleanProperty.getBean();
if (focus) { // gained focus
initialValue = textField.getText();
} else { // lost focus
trimText(textField);
if(!textField.getText().equals(initialValue)){ //text was changed
if(textField.getText().isEmpty()){ //text was erased
switch (textField.getId()) {
case CoreConstants.AREA_SIZE_TF:
emptyAreaSize();
break;
case CoreConstants.NUMBER_OF_PEOPLE_TF:
emptyPeopleLiving();
break;
default:
//no actions
break;
}
}else{
switch (textField.getId()) {
case CoreConstants.NAME_TF:
System.out.println("Execute name code!");
break;
case CoreConstants.SURNAME_TF:
System.out.println("Execute last name code!");
break;
case CoreConstants.MAIL_TF:
System.out.println("Execute mail code!");
break;
default:
//no actions
break;
}}}}}};
nameTextField.focusedProperty().addListener(focusHandler);
surnameTextField.focusedProperty().addListener(focusHandler);
Or by creating separate ChangeListener for each of TextFields like this:
numberOfPeopleTextField.focusedProperty().addListener(new ChangeListener<Boolean>() {
private String initialValue = null;
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean focus) {
if (focus) { // gained focus
initialValue = numberOfPeopleTextField.getText();
} else { // lost focus
trimText(numberOfPeopleTextField);
if(!numberOfPeopleTextField.getText().equals(initialValue)){ //text was changed
if(numberOfPeopleTextField.getText().isEmpty()){
emptyPeopleLiving();
}else{
mailInfoUpdate("mail");
}
}
}
}
});
mailTextField.focusedProperty().addListener(new ChangeListener<Boolean>() {
private String initialValue = null;
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean focus) {
if (focus) { // gained focus
initialValue = mailTextField.getText();
} else { // lost focus
trimText(mailTextField);
if(!mailTextField.getText().equals(initialValue)){ //text was changed
mailInfoUpdate("mail");
}
}
}
});
Creating several ChangeListeners VS one shared ChangeListener what is better to use and why?

I'd go with option 3: Create a class containing the common code and use different handlers.
class FocusChangeListener implements ChangeListener<Boolean> {
private final TextField textField;
private final Consumer<? super String> changeHandler;
private String initialValue = null;
FocusChangeListener(TextField textField, Consumer<? super String> changeHandler) {
this.textField = textField;
this.changeHandler = changeHandler;
}
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean focus) {
if (focus) { // gained focus
initialValue = textField.getText();
} else { // lost focus
trimText(textField);
if(changeHandler != null && !textField.getText().equals(initialValue)) { //text was changed
changeHandler.accept(textField.getText());
}
}
}
}
numberOfPeopleTextField.focusedProperty().addListener(new FocusChangeListener(numberOfPeopleTextField, text -> {
if(text.isEmpty()){
emptyPeopleLiving();
} else{
mailInfoUpdate("mail");
}
}));
mailTextField.focusedProperty().addListener(new FocusChangeListener(mailTextField, text -> mailInfoUpdate("mail")));
If it suits your needs better, you could also replace the Consumer with a abstract method in FocusChangeListener...
Switching on the id is a bad idea since it does not seperate the concerns in addition to using magic strings. Reimplementing the whole listener is not a good idea either since it makes the code harder to maintain...

Related

How to make TextFieldTableCell conditional on model property?

I have a TableView with an editable TextFieldTableCell that I want to restrict to be available based on a BooleanProperty of my model object.
For example, textField.disableProperty().bind(item.editableProperty().not())
Currently, I have the basic implementation from the Oracle docs:
colComment.setCellFactory(TextFieldTableCell.forTableColumn());
colComment.setOnEditCommit(event -> event.getTableView().getItems().get(
event.getTablePosition().getRow()).setComment(
event.getNewValue())
);
This obviously does not allow much flexibility. The desire is to check the item's editableProperty and if it is true, display the TextFieldTableCell and bind it to the item's commentProperty.
If that property is false, the cell should simply display the value of the commentProperty.
I have not worked with editable TableViews in the past so I am a bit lost.
I have tried to hack out a workaround with manually setting the graphic myself, but that just does nothing with the cell:
colComment.setCellFactory(cell -> new TableCell<LogEntry, String>() {
final TextField txtComment = new TextField();
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
LogEntry logEntry = (LogEntry) getTableRow().getItem();
if (logEntry.isEditable()) {
txtComment.textProperty().bindBidirectional(logEntry.commentProperty());
setGraphic(txtComment);
} else {
setText(item);
}
}
}
});
The basic approach is to disallow cell's editing based on a condition. TextFieldTableCell has no direct support for such, but can be extended just as any other type of cell. Options are
override startEdit to do nothing if the condition is not met
bind the cell's editability property to a condition of the rowItem
The most simple is the first (the latter is a bit more involved, due to requiring updates when parent TableRow and its item changes). A quick example (all boiler-plate except the cell ;):
public class TableCellConditionalEditable extends Application {
/**
* Cell with custom condition to prevent editing.
*/
public static class ConditionalEditableCell extends TextFieldTableCell<ConditionalWritable, String> {
public ConditionalEditableCell() {
super(new DefaultStringConverter());
}
/**
* Overridden to do nothing if rowItem-related condition not met.
*/
#Override
public void startEdit() {
if (!isConditionalEditable()) return;
super.startEdit();
}
private boolean isConditionalEditable() {
if (getTableRow() == null || getTableRow().getItem() == null || isEmpty()) return false;
return getTableRow().getItem().writableProperty().get();
}
}
private Parent createContent() {
TableView<ConditionalWritable> table = new TableView<>(ConditionalWritable.conditionalWritables());
TableColumn<ConditionalWritable, String> text = new TableColumn<>("Text");
text.setCellValueFactory(cc -> cc.getValue().textProperty());
TableColumn<ConditionalWritable, Boolean> writable = new TableColumn<>("Writable");
writable.setCellValueFactory(cc -> cc.getValue().writableProperty());
table.getColumns().addAll(text, writable);
table.setEditable(true);
text.setCellFactory(cc -> new ConditionalEditableCell());
BorderPane content = new BorderPane(table);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class ConditionalWritable {
private SimpleStringProperty text;
private SimpleBooleanProperty writable;
public ConditionalWritable(String text, boolean writable) {
this.text = new SimpleStringProperty(text);
this.writable = new SimpleBooleanProperty(writable);
}
public StringProperty textProperty() {
return text;
}
public BooleanProperty writableProperty() {
return writable;
}
public static ObservableList<ConditionalWritable> conditionalWritables() {
return FXCollections.observableArrayList(
new ConditionalWritable("some data", false),
new ConditionalWritable("other data", true),
new ConditionalWritable("nothing important", true)
);
}
}
}

How disable move focus from textField in JavaFX

Is there possibility to keep focus on textField in JavaFX?
I do validation on textField using listener.
textField.textProperty().addListener(
new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (!validate(newValue)) {
textField.setStyle("-fx-text-fill: green;");
textField.requestFocus();
} else {
textField.setStyle("-fx-text-fill: black;");
}
}
}
);
It is function which returns boolean depending on textField value validity. If value is not valid then I change text color to RED. Then I want to keep focus on invalid textField and force user to correct value.
Can it be done?
Thanks in advance.
Also use a listener for the focused property which takes back the focus, when it's moved somewhere else:
ChangeListener<Boolean> focusLossListener = (observable, wasFocused, isFocused) -> {
if (!isFocused) {
tf.requestFocus();
}
};
textField.textProperty().addListener(
new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
textField.focusedProperty().removeListener(focusLossListener);
if (!validate(newValue)) {
textField.focusedProperty().addListener(focusLossListener);
textField.setStyle("-fx-text-fill: green;");
textField.requestFocus();
} else {
textField.setStyle("-fx-text-fill: black;");
}
}
}
);

Table cell combobox - action not performed

I'm applying the below cell factory to a column.
targetEnviroment.setCellFactory(new Callback<TableColumn<DevWorkTabBench, String>, TableCell<DevWorkTabBench, String>>() {
#Override
public TableCell<DevWorkTabBench, String> call(TableColumn<DevWorkTabBench, String> param) {
TableCell<DevWorkTabBench, String> cell = new TableCell<DevWorkTabBench, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
String status = null;
try {
status = getTableView().getItems().get(getIndex()).getObjectStatus();
} catch (IndexOutOfBoundsException ex) {
status = "";
}
if (status.equalsIgnoreCase("ReadyForDeployment")) {
ComboBox<String> comboBox = new ComboBox(environmentList);
comboBox.valueProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
commitEdit(newValue);
}
});
comboBox.setOnShown(new EventHandler<Event>() {
#Override
public void handle(Event event) {
getTableView().edit(getIndex(), getTableColumn());
getTableView().getSelectionModel().select(getIndex());
}
});
comboBox.setValue(item);
setGraphic(comboBox);
} else {
setGraphic(null);
}
if (empty) {
setGraphic(null);
}
}
};
return cell;
}
});
When I change the status to the mentioned status, I get the look of ComboBox in that particular cell but the drop down does not occur. Even after multiple clicks no action seems to be performed on the combobox. I do not get any exception other than the handled one. Other columns are editable and performing task as expected.
I have no idea what is wrong here. Can anyone please help me.
Since you are always displaying the combo box in the (non-empty) cells, you don't really need to go into "editing" mode as the standard TextFieldTableCell etc does. Your implementation is more similar to the CheckBoxTableCell, which essentially bypasses the editing mechanism. From the documentation for that class:
Note that the CheckBoxTableCell renders the CheckBox 'live', meaning
that the CheckBox is always interactive and can be directly toggled by
the user. This means that it is not necessary that the cell enter its
editing state (usually by the user double-clicking on the cell). A
side-effect of this is that the usual editing callbacks (such as on
edit commit) will not be called. If you want to be notified of
changes, it is recommended to directly observe the boolean properties
that are manipulated by the CheckBox.
So your cell implementation behaves similarly: don't invoke edit(...) (which I think is messing things up) and don't rely on the commitEdit(...), cancelEdit() etc methods (which won't work as you're not in editing state), but just update the model class directly.
I can't test, since there isn't a MCVE to work from, so this might not work directly, but it should be enough to get you started toward something that will work.
targetEnviroment.setCellFactory(new Callback<TableColumn<DevWorkTabBench, String>, TableCell<DevWorkTabBench, String>>() {
#Override
public TableCell<DevWorkTabBench, String> call(TableColumn<DevWorkTabBench, String> param) {
TableCell<DevWorkTabBench, String> cell = new TableCell<DevWorkTabBench, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null) ;
} else {
String status = getTableView().getItems().get(getIndex()).getObjectStatus();
if (status.equalsIgnoreCase("ReadyForDeployment")) {
ComboBox<String> comboBox = new ComboBox(environmentList);
comboBox.valueProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
//commitEdit(newValue);
getTableView().getItems().get(getIndex()).setObjectStatus(newValue);
}
});
comboBox.setValue(item);
setGraphic(comboBox);
} else {
setGraphic(null);
}
}
}
};
return cell;
}
});

Update validator

I'm using this code to validate TextField for network port.
fieldNport.textProperty().addListener(new ChangeListener<String>()
{
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
IpAddressNameValidator.hide();
if (!newValue.matches("-?\\d+(\\.\\d+)?"))
{
MenuItem cc = new MenuItem(newValue + " is not correct network port");
cc.getStyleClass().add("validator-item");
IpAddressNameValidator.getItems().clear();
IpAddressNameValidator.getItems().add(cc);
IpAddressNameValidator.show(fieldNport, Side.RIGHT, 10, 0);
}
}
});
I noticed that the validator is not updated when I delete the old value with backspace. The only solution that I found is this IpAddressNameValidator.hide(); and then show the validator message again.
I there other way to refresh the validator message when I add or remove values? This solution works but the message is blinking when I add new values.
Every time the text changes and the regex expression matches then you are unnecessarily recreating the MenuItem etc. Rather do it like this:
fieldNport.textProperty().addListener(new ChangeListener<String>()
{
private MenuItem cc = new MenuItem();
{
cc.getStyleClass().add("validator-item");
ipAddressNameValidator.getItems().clear();
ipAddressNameValidator.getItems().add(cc);
}
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
if (!newValue.matches("-?\\d+(\\.\\d+)?"))
{
if ( ! ipAddressNameValidator.isShowing() )
{
ipAddressNameValidator.show(fieldNport, Side.RIGHT, 10, 0);
}
cc.setText( newValue + " is not correct network port" );
}
}
});

JAVAFX event triggered when selecting a check box

My JavaFx FXML application has an issue.
When I select a checkbox on a form, I want to execute a method based on the checkbox that was clicked. Is there any way that I can pass the name of the checkbox through to the method so I can perform some conditional work on it?
I have two checkboxes and only one can be selected. When I click on one, the other should be de-selected and vice versa. Obviously the code below will not work so I am looking to pass the name of the object that was clicked.
Any help would be appreciated,
many thanks.
#FXML private void updateRcs(){
if (chkRcsuri.isSelected()){
chkRcsuri2.setSelected(false);
}
if (chkRcsuri2.isSelected()){
chkRcsuri.setSelected(false);
}
}
You can use change tracking or use Event handling mechanism of JavaFX.
With checkboxes like this,
final CheckBox chk1 = new CheckBox("chk 1");
final CheckBox chk2 = new CheckBox("chk 2");
Change tracking
chk1.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
chk2.setSelected(!newValue);
}
});
chk2.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
chk1.setSelected(!newValue);
}
});
Using event handling
EventHandler eh = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (event.getSource() instanceof CheckBox) {
CheckBox chk = (CheckBox) event.getSource();
System.out.println("Action performed on checkbox " + chk.getText());
if ("chk 1".equals(chk.getText())) {
chk2.setSelected(!chk1.isSelected());
} else if ("chk 2".equals(chk.getText())) {
chk1.setSelected(!chk2.isSelected());
}
}
}
};
chk1.setOnAction(eh);
chk2.setOnAction(eh);
Wouldn't radio buttons give you a mutually exclusive selection? Just make sure you set the groupname to be the same - selecting one would then automatically de-select the other and you can just put additional logic in the Action event.
Better than trying to re-write same functionality around checkboxes.
So I was trying to do a similar thing, except I had multiple checkboxes and then one that would be nonsensical to have selectable in conjuction with the others. I made two seperate listeners and set one general purpose one to the main boxes, and a specialized one to the exception.
#FXML private CheckBox redCB = new CheckBox();
#FXML private CheckBox blueCB = new CheckBox();
#FXML private CheckBox greenCB = new CheckBox();
#FXML private CheckBox whiteCB = new CheckBox();
#FXML private CheckBox blackCB = new CheckBox();
#FXML private CheckBox colorlessCB = new CheckBox();
//assigning listeners
redCB.selectedProperty().addListener(colorCheckChange);
blueCB.selectedProperty().addListener(colorCheckChange);
greenCB.selectedProperty().addListener(colorCheckChange);
whiteCB.selectedProperty().addListener(colorCheckChange);
blackCB.selectedProperty().addListener(colorCheckChange);
colorlessCB.selectedProperty().addListener(colorlessCheckChange);
//note: this is the only different one^^^
//making listeners
ChangeListener colorCheckChange = new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> ov,
Boolean old_val, Boolean new_val) {
if (new_val)
colorlessCB.setSelected(false);
}};
ChangeListener colorlessCheckChange = new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> ov,
Boolean old_val, Boolean new_val) {
if (new_val)
{
redCB.setSelected(false);
blueCB.setSelected(false);
greenCB.setSelected(false);
blackCB.setSelected(false);
whiteCB.setSelected(false);
}
}
};
The first one basically just makes sure that colorlessCB isn't selected while trying to select the other colors, and vice verca. This way you also avoid the problem of de-selecting one, and the other one automatically reselecting itself.
This is my solution. But be sure about the a variable to be appropriate for you
//First in FXML file
<CheckBox fx:id="chkbxAuto" mnemonicParsing="false" onAction="#autoConfig" text="Auto" />
// in controler
public class FXMLController implements Initializable {
private static int a = 0;
//references to lables
....
#FXML
private Label lblStateValue;
#FXML
private Group grpSetting; // a group of elements which I want to be disabled and enabled
...
#FXML
private void autoConfig(ActionEvent event) {
System.out.println("Configing automatically");
a++;
if(a%2==1){
lblStateValue.setText("Auto configuration enabled"); // it change a lable to show the state
grpSetting.setDisable(true); // it disable a group of elements
}
else{
lblStateValue.setText("Auto configuration disabled");
grpSetting.setDisable(false);
}
a%=10;
}
...
I know this question is pretty old, but I found when looking for the same problem. I found a solution which
a) seems clearer (at least to me comparing to the listener definition) for reading source code and
b) Defining a changeListener on a checkBox gave me some problems.
Anyway my solution was to define an onAction function for the checkbox.
yourCheckboxName.setOnAction(this::aFunctionName);
...
void aFunctionName() {
if(yourCheckboxName.isSelected()) {
doThis()
} else {
doThat()
}
}
Attention This needs Java8 or higher.
None of the above options take advantage of the most commpact lambda expresions that can be used to add this very repetitive listeners.
Let suppose you have two checkboxes:
chk1.selectedProperty().addListener((observable, oldValue, newValue) -> chk2.setSelected(!newValue));
chk2.selectedProperty().addListener((observable, oldValue, newValue) -> chk1.setSelected(!newValue));

Resources