Youssef Idraiss
Youssef Idraiss

Reputation: 426

How to use Bindings.when to bind button disableProperty with a TableView Selecteditem property

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()

Upvotes: 0

Views: 528

Answers (2)

kleopatra
kleopatra

Reputation: 51535

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());

Upvotes: 1

James_D
James_D

Reputation: 209684

You can use a custom binding which implements a listener: for example:

transferButton.disableProperty().bind(new BooleanBinding() {
    {
        tableView.getSelectionModel().selectedItemProperty().addListener(obs, oldSelection, newSelection) -> {
            if (oldSelection != null) unbind(oldSelection.validProperty());
            if (newSelection != null) bind(newSelection.validProperty());
            invalidate();
        });
        bind(tableView.getSelectionModel().selectedItemProperty());
    }

    @Override
    protected boolean computeValue() {
        Model selection = tableView.getSelectionModel().getSelectedItem();
        if (selection == null) return true ;
        return ! selection.isValid();
    }
});

There is also a selection API in the Bindings API which will work, though it is not robust and will generate spurious warnings when the selection is null:

transferButton.disableProperty().bind(Bindings.selectBoolean(
    tableView.getSelectionModel().selectedItemProperty(),
    "valid"
)).not());

Upvotes: 2

Related Questions