Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX Boolean Binding Based on Selected Items in a TableView

I am attempting to enable a JavaFX Button depending on the aggregate of a property value in the selected rows in a TableView. The following is an example application that demonstrates the problem:

package test;

import java.util.Random;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(final String[] args) throws Exception {
        launch(args);
    }

    private static class Row {
        private final BooleanProperty myProp;
        public Row(final boolean value) {
            myProp = new SimpleBooleanProperty(value);
        }
        public BooleanProperty propProperty() { return myProp; }
    }

    @Override
    public void start(final Stage window) throws Exception {
        // Create a VBox to hold the table and button
        final VBox root = new VBox();
        root.setMinSize(200, 200);

        // Create the table, and enable multi-select
        final TableView<Row> table = new TableView<>();
        final MultipleSelectionModel<Row> selectionModel = table.getSelectionModel();
        selectionModel.setSelectionMode(SelectionMode.MULTIPLE);
        root.getChildren().add(table);

        // Create a column based on the value of Row.propProperty()
        final TableColumn<Row, Boolean> column = new TableColumn<>("Value");
        column.setCellValueFactory(p -> p.getValue().propProperty());
        table.getColumns().add(column);

        // Add a button below the table
        final Button button = new Button("Button");
        root.getChildren().add(button);

        // Populate the table with true/false values
        final ObservableList<Row> rows = table.getItems();
        rows.addAll(new Row(false), new Row(false), new Row(false));

        // Start a thread to randomly modify the row values
        final Random rng = new Random();
        final Thread thread = new Thread(() -> {
            // Flip the value in a randomly selected row every 10 seconds
            try {
                do {
                    final int i = rng.nextInt(rows.size());
                    System.out.println("Flipping row " + i);
                    Thread.sleep(10000);
                    final BooleanProperty prop = rows.get(i).propProperty();
                    prop.set(!prop.get());
                } while (true);
            } catch (final InterruptedException e) {
                System.out.println("Exiting Thread");
            }
        }, "Row Flipper Thread");
        thread.setDaemon(true);
        thread.start();

        // Bind the button's disable property such that the button
        //     is only enabled if one of the selected rows is true
        final ObservableList<Row> selectedRows = selectionModel.getSelectedItems();
        button.disableProperty().bind(Bindings.createBooleanBinding(() -> {
            for (int i = 0; i < selectedRows.size(); ++i) {
                if (selectedRows.get(i).propProperty().get()) {
                    return false;
                }
            }
            return true;
        }, selectedRows));

        // Show the JavaFX window
        final Scene scene = new Scene(root);
        window.setScene(scene);
        window.show();
    }
}

To test, start the above application, and select the row indicated by the text "Flipping row N", where N is in [0, 2]. When the value of the selected row changes to true...

Observed Behavior button remains disabled.
Desired Behavior button becomes enabled.

Does anyone know how to create a BooleanBinding that exhibits the desired behavior?

like image 733
Jeff G Avatar asked Sep 21 '16 19:09

Jeff G


People also ask

How to set Data in TableView in JavaFX?

TableView is a component that is used to create a table populate it, and remove items from it. You can create a table view by instantiating thejavafx. scene. control.

How to select a row in TableView JavaFX?

To select a row with a specific index you can use the select(int) method. Here is an example of selecting a single row with a given index in a JavaFX TableView: selectionModel. select(1);

How to select multiple rows in TableView in JavaFX?

I would add a multi-select button like android. Click the button the subsequent selections get added (or maybe removed after another click) to a list of selections.


1 Answers

Your binding needs to be invalidated if any of the propPropertys of the selected rows change. Currently the binding is only observing the selected items list, which will fire events when the list contents change (i.e. items become selected or unselected) but not when properties belonging to items in that list change value.

To do this, create a list with an extractor:

final ObservableList<Row> selectedRows = 
    FXCollections.observableArrayList(r -> new Observable[]{r.propProperty()});

This list will fire events when items are added or removed, or when the propProperty() of any item in the list changes. (If you need to observe multiple values, you can do so by including them in the array of Observables.)

Of course, you still need this list to contain the selected items in the table. You can ensure this by binding the content of the list to the selectedItems of the selection model:

Bindings.bindContent(selectedRows, selectionModel.getSelectedItems());

Here is a version of your MCVE using this:

import java.util.Random;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(final String[] args) throws Exception {
        launch(args);
    }

    private static class Row {
        private final BooleanProperty myProp;
        public Row(final boolean value) {
            myProp = new SimpleBooleanProperty(value);
        }
        public BooleanProperty propProperty() { return myProp; }
    }

    @Override
    public void start(final Stage window) throws Exception {
        // Create a VBox to hold the table and button
        final VBox root = new VBox();
        root.setMinSize(200, 200);

        // Create the table, and enable multi-select
        final TableView<Row> table = new TableView<>();
        final MultipleSelectionModel<Row> selectionModel = table.getSelectionModel();
        selectionModel.setSelectionMode(SelectionMode.MULTIPLE);
        root.getChildren().add(table);

        // Create a column based on the value of Row.propProperty()
        final TableColumn<Row, Boolean> column = new TableColumn<>("Value");
        column.setCellValueFactory(p -> p.getValue().propProperty());
        table.getColumns().add(column);

        // Add a button below the table
        final Button button = new Button("Button");
        root.getChildren().add(button);

        // Populate the table with true/false values
        final ObservableList<Row> rows = table.getItems();
        rows.addAll(new Row(false), new Row(false), new Row(false));

        // Start a thread to randomly modify the row values
        final Random rng = new Random();
        final Thread thread = new Thread(() -> {
            // Flip the value in a randomly selected row every 10 seconds
            try {
                do {
                    final int i = rng.nextInt(rows.size());
                    System.out.println("Flipping row " + i);
                    Thread.sleep(10000);
                    final BooleanProperty prop = rows.get(i).propProperty();
                    Platform.runLater(() -> prop.set(!prop.get()));
                } while (true);
            } catch (final InterruptedException e) {
                System.out.println("Exiting Thread");
            }
        }, "Row Flipper Thread");
        thread.setDaemon(true);
        thread.start();


        // Bind the button's disable property such that the button
        //     is only enabled if one of the selected rows is true
        final ObservableList<Row> selectedRows = 
                FXCollections.observableArrayList(r -> new Observable[]{r.propProperty()});
        Bindings.bindContent(selectedRows, selectionModel.getSelectedItems());
        button.disableProperty().bind(Bindings.createBooleanBinding(() -> {
            for (int i = 0; i < selectedRows.size(); ++i) {
                if (selectedRows.get(i).propProperty().get()) {
                    return false;
                }
            }
            return true;
        }, selectedRows));

        // Show the JavaFX window
        final Scene scene = new Scene(root);
        window.setScene(scene);
        window.show();
    }
}
like image 50
James_D Avatar answered Nov 14 '22 22:11

James_D