Reputation: 979
In this JavaFX tutorial, address book application creation suggested. Registered person could be deleted, however before deleting, person must be selected in table view.
There will be an ArrayIndexOutOfBoundsException because it could not remove a person item at index -1. The index -1 was returned by getSelectedIndex() - which means that there was no selection.
To ignore such an error is not very nice, of course. We should let the user know that he/she must select a person before deleting. (Even better would be if we disabled the button so that the user doesn’t even have the chance to do something wrong.)
Author is totally correct about "Even better would be if we disabled the button ...", however he selected the first way. I suppose, manipulation of the button state is the essential skill for JavaFX application development, so I tried to implement the better solution.
Of course, we can do it by way like this:
peopleTable.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
showPersonDetails(newValue);
boolean somebodySelected = peopleTable.getSelectionModel().getSelectedIndex() >= 0;
button.setDisable(!somebodySelected);
}
);
How ever I am interested for the other way: using dynamic attribute value for button:
<Button
mnemonicParsing="false"
onAction="#handleDeletePerson"
text="Delete"
disable="disableDeleteButtonFlag"
/>
If dynamic attribute value is possible, there is no need explicitly invoke button.setDisable()
anymore. However, below code does not work.
@FXML
private boolean disableDeleteButtonFlag = true;
// ...
@FXML
private void initialize() {
// ...
peopleTable.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
showPersonDetails(newValue);
disableDeleteButtonFlag = peopleTable.getSelectionModel().getSelectedIndex() < 0;
}
);
}
Upvotes: 0
Views: 703
Reputation: 46116
First off, I'm not sure if referencing a boolean
field that way would even let the FXML file be loaded (you don't specific exactly how it "doesn't work"). But ignoring that, fields are not observable in Java which means updating the field will not automagically cause the disable
property of the Button
to be updated. This is why JavaFX has the [ReadOnly]Property
interfaces and introduces the "JavaFX Bean" (an extension to Java Bean that adds a "property getter"); it allows observers to see changes in an object's properties. Note you can bind a writable property to an ObservableValue
so it always has the same value.
Now, I would have expected an expression binding would be what you're looking for, but the following:
<ListView fx:id="list"/>
<Button disable="${list.selectionModel.selectedIndex == -1}"/>
Doesn't appear to work—I get an exception (using JavaFX 12.0.1):
Caused by: java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.Integer (java.lang.Long and java.lang.Integer are in module java.base of loader 'bootstrap')
at java.base/java.lang.Integer.compareTo(Integer.java:64)
at javafx.fxml/com.sun.javafx.fxml.expression.Expression.lambda$equalTo$5(Expression.java:1105)
at javafx.fxml/com.sun.javafx.fxml.expression.BinaryExpression.evaluate(BinaryExpression.java:55)
at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.getValue(ExpressionValue.java:192)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
at javafx.base/javafx.beans.value.ObservableValueBase.addListener(ObservableValueBase.java:55)
at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.addListener(ExpressionValue.java:201)
at javafx.base/javafx.beans.binding.BooleanBinding.bind(BooleanBinding.java:106)
at javafx.base/javafx.beans.property.BooleanPropertyBase$ValueWrapper.<init>(BooleanPropertyBase.java:254)
at javafx.base/javafx.beans.property.BooleanPropertyBase.bind(BooleanPropertyBase.java:168)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:326)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:775)
at javafx.fxml/javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2838)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2557)
... 17 more
Using the selectedItem
property instead:
<Button disable="${list.selectionModel.selectedItem == null}"/>
Gives a different exception:
Caused by: java.lang.NullPointerException
at javafx.fxml/com.sun.javafx.fxml.expression.Expression.lambda$equalTo$5(Expression.java:1105)
at javafx.fxml/com.sun.javafx.fxml.expression.BinaryExpression.evaluate(BinaryExpression.java:55)
at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.getValue(ExpressionValue.java:192)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
at javafx.base/javafx.beans.value.ObservableValueBase.addListener(ObservableValueBase.java:55)
at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.addListener(ExpressionValue.java:201)
at javafx.base/javafx.beans.binding.BooleanBinding.bind(BooleanBinding.java:106)
at javafx.base/javafx.beans.property.BooleanPropertyBase$ValueWrapper.<init>(BooleanPropertyBase.java:254)
at javafx.base/javafx.beans.property.BooleanPropertyBase.bind(BooleanPropertyBase.java:168)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:326)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:775)
at javafx.fxml/javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2838)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2557)
... 17 more
As both of these exceptions are emanating from JavaFX code (i.e. not our code) this behavior is probably a bug. That, or I'm using expression bindings incorrectly—in which case I hope someone corrects me. Since attempting to bind the property in FXML is not working, the workaround is to bind the property in code.
<VBox xmlns="http://javafx.com/" xmlns:fx="http://javafx.com/fxml" fx:controller="Controller">
<ListView fx:id="list"/>
<Button fx:id="button"/>
</VBox>
public class Controller {
@FXML private ListView<YourType> list;
@FXML private Button button;
@FXML
private void initialize() {
button.disableProperty()
.bind(list.getSelectionModel().selectedIndexProperty().isEqualTo(-1));
}
}
The isEqualTo
method will return a BooleanBinding
whose value is true
whenever the selectedIndex
property holds -1
. The disable
property is then bound to this BooleanBinding
so it always has the same value. You can read Using JavaFX Properties and Binding for more information.
Upvotes: 2