Issue
I have a TableView that holds a Model class, which has a BooleanProperty as follow
@FXML
TableView<Model> tableView;
Model Class :
class Model{
BooleanProperty valid;
public Model()
{
valid = new SimpleBooleanProperty();
}
... getters and setters
}
What i want to acheive is to bind a button disable property with selected item valid Property in the Model class fom the tableView, i know that i can acheive that with listeners, but using a listener needs to set first the initial value properly, since they are not getting fired until there is some change, as an exemple in this case, if there is no selected item from the table and the button is set to be not disable from the start, it will still be like that, until the listener fired, this is why i prefer to use Bindings, since it doesn't care about the initial value. is there any way to do so with Bindings also ?
what i tried :
i tried this :
transferButton.disableProperty().bind(Bindings.when(tableView.getSelectionModel().selectedItemProperty().isNotNull()).then(
tableView.getSelectionModel().getSelectedItem().valideProperty()
).otherwise(false));
but the problem is that i'm getting the following error :
return value of "javafx.scene.control.TableView$TableViewSelectionModel.getSelectedItem()" is null
Even tho i put a condition to the binding : Bindings.when(tableView.getSelectionModel().selectedItemProperty().isNotNull()
Solution
Here's an approach of a custom select binding which uses functions to provide nested properties (similar to core SelectBinding, just replacing the reflective access to the nested properties by functions providing them)
The basic idea
- start with binding to the root
- keep the binding chain in the dependencies
- update the binding chain on validating (no need to do anything as long as the binding is not valid)
- implement state cleanup
Code example (here with a single function only, can be extended for a longer chain, though, by adding more functions and walk the providers)
/**
* Custom binding to a nested property using a Function to provide the nested.
*/
public class XSelectBinding<R, T> extends ObjectBinding<T> {
private ObservableList<ObservableValue<?>> dependencies;
private Function<R, ObservableValue<T>> provider;
public XSelectBinding(ObservableValue<R> root, Function<R, ObservableValue<T>> provider) {
if (root == null) {
throw new NullPointerException("root must not be null");
}
if (provider == null) {
throw new NullPointerException("provider must not be null");
}
dependencies = FXCollections.observableArrayList(root);
this.provider = provider;
bind(root);
}
/**
* Implemented to update dependencies and return the value of the nested property if
* available
*/
@Override
protected T computeValue() {
onValidating();
ObservableValue<?> child = dependencies.size() > 1 ? dependencies.get(1) : null;
return child != null ? (T) child.getValue() : null;
}
/**
* Updates dependencies and bindings on validating.
*/
protected void onValidating() {
// grab the root
ObservableValue<R> root = (ObservableValue<R>) dependencies.get(0);
// cleanup bindings and dependencies
unbindAll();
// rebind starting from root
dependencies.add(root);
ObservableValue<T> nestedProperty = root.getValue() != null ?
provider.apply(root.getValue()) : null;
if (nestedProperty != null) {
dependencies.add(nestedProperty);
}
bind(dependencies.toArray(new ObservableValue<?>[dependencies.size()]));
}
/**
* Unbinds and clears dependencies.
*/
private void unbindAll() {
unbind(dependencies.toArray(new ObservableValue<?>[dependencies.size()]));
dependencies.clear();
}
@Override
public ObservableList<?> getDependencies() {
return FXCollections.unmodifiableObservableList(dependencies);
}
/**
* Implemented to unbind all dependencies and clear references to path providers.
*/
@Override
public void dispose() {
unbindAll();
provider = null;
}
}
To use in the OP's context:
// XSelectBinding
ObjectBinding<Boolean> xSelectBinding = new XSelectBinding<Model, Boolean>(
table.getSelectionModel().selectedItemProperty(),
item -> item.validProperty());
transferButton.disableProperty().bind(BooleanExpression.booleanExpression(xSelectBinding).not());
Answered By - kleopatra
Answer Checked By - Katrina (JavaFixing Volunteer)