JavaFX: How to disable a row in a TableView?
Asked Answered
T

1

7

I want to disable a row in a TableView. I have a Tableview of Products, and I already know which product needs to be disabled (I got the index of it from the ObservableList that fills the TableView).

How do I get the TableRow that is associated with Product in the ObservableList, of which I know the index?

Otherwise: is there a easy way to disable a specific TableRow from a TableView?

Any help is greatly appreciated.

Trentontrepan answered 28/10, 2014 at 11:25 Comment(2)
what do you mean by "disable"?Columbary
tableRow.setDisable(), but I can not get the associated TableRow.Trentontrepan
D
9

The best way is not to use the index, but to use a custom row factory and observe the appropriate properties of the item in the row.

This is slightly tricky with the current API, as you probably need to observe a property of the item property of the table row. You can use Bindings.select(...) to do this, but the current version spews out lots of superfluous warnings when the item is null (which it will be quite frequently). I prefer to use the EasyBind framework for this kind of functionality.

This example disables all table rows for which the value property of the displayed item is less than 5:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import org.fxmisc.easybind.EasyBind;

public class DisabledTableRowExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Item> table = new TableView<>();
        table.getItems().addAll(createData());

        TableColumn<Item, Item> deleteCol = createTableColumn("Delete", ReadOnlyObjectWrapper<Item>::new);
        deleteCol.setCellFactory(this::createDeleteCell);

        table.getColumns().addAll(Arrays.asList(
                createTableColumn("Name", Item::nameProperty),
                createTableColumn("Value", Item::valueProperty),
                deleteCol 
        ));

        // A row factory that returns a row that disables itself whenever the
        // item it displays has a value less than 5:

        table.setRowFactory(tv -> {
            TableRow<Item> row = new TableRow<>();

            // use EasyBind to access the valueProperty of the itemProperty of the cell:
            row.disableProperty().bind(
                    EasyBind.select(row.itemProperty()) // start at itemProperty of row
                    .selectObject(Item::valueProperty)  // map to valueProperty of item, if item non-null
                    .map(x -> x.intValue() < 5) // map to BooleanBinding via intValue of value < 5
                    .orElse(false)); // value to use if item was null

            // it's also possible to do this with the standard API, but there are lots of 
            // superfluous warnings sent to standard out:
            // row.disableProperty().bind(
            //          Bindings.selectInteger(row.itemProperty(), "value")
            //          .lessThan(5));

            return row ;
        });
        BorderPane root = new BorderPane(table);
        Scene scene = new Scene(root, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private List<Item> createData() {
        Random rng = new Random();
        List<Item> data = new ArrayList<>();
        for (int i=1; i<=20; i++) {
            data.add(new Item("Item "+i, rng.nextInt(10)));
        }
        return data ;
    }

    private <S,T> TableColumn<S, T> createTableColumn(String name, Function<S, ObservableValue<T>> propertyMapper) {
        TableColumn<S,T> col = new TableColumn<>(name);
        col.setCellValueFactory(cellData -> propertyMapper.apply(cellData.getValue()));
        return col ;
    }

    private TableCell<Item, Item> createDeleteCell(TableColumn<Item, Item> col) {
        ObservableList<Item> itemList = col.getTableView().getItems();
        TableCell<Item, Item> cell = new TableCell<>();
        cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        Button button = new Button("Delete");
        button.setOnAction(event -> itemList.remove(cell.getItem()));
        cell.graphicProperty().bind(Bindings.when(cell.emptyProperty()).then((Node)null).otherwise(button));
        return cell ;
    }

    public static class Item {
        private final StringProperty name = new SimpleStringProperty(this, "name");
        private final IntegerProperty value = new SimpleIntegerProperty(this, "value");
        public final StringProperty nameProperty() {
            return this.name;
        }
        public final java.lang.String getName() {
            return this.nameProperty().get();
        }
        public final void setName(final java.lang.String name) {
            this.nameProperty().set(name);
        }
        public final IntegerProperty valueProperty() {
            return this.value;
        }
        public final int getValue() {
            return this.valueProperty().get();
        }
        public final void setValue(final int value) {
            this.valueProperty().set(value);
        }

        public Item(String name, int value) {
            setName(name);
            setValue(value);
        }
    }

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

If you're really disabling based on the index, you can use a very similar technique:

IntegerProperty disabledRowIndex = new SimpleIntegerProperty();
// ...
// in row factory do:
row.disableProperty().bind(row.indexProperty.isEqualTo(disabledRowIndex));

Then calling disabledRowIndex.set(...) will disable the row at the supplied index.

Notice that the disable semantics might not be exactly what you want. This disables all input to the table row (so, for example, the delete button will not be enabled); however it doesn't prevent selection of the row (keyboard navigation is managed by the tableview itself, so you can still select the row using the keyboard). Defining custom selection behavior is more challenging.

Delft answered 28/10, 2014 at 12:34 Comment(9)
Thanks for your example! Is it working as it is stated above. I have one minor problem: I can only disable one row at the time, and I would like to disable multiple rows. How can I do this?Trentontrepan
The example I have disables multiple rows... your best bet is to formulate the criteria for the row to be disabled in terms of properties in the table's model objects (instead of using indexes). If you really want to use indexes, create an ObservableSet<Integer> containing the disabled indexes, and then bind to a custom BooleanBinding that uses disabledIndexes.contains(cell.getIndex()). But if you're doing it that way, I'd strongly suggest redesigning it to make it cleaner.Delft
Okay, I will try that, but I am not able to use EasyBind. I have imported the JAR, but Netbeans doesn't recognize the import: import org.fxmisc.easybind.EasyBind;Trentontrepan
That's just a question about "How do I use a third party jar in Netbeans." I don't use Netbeans, so I can't help with that, but it should be simple enough.Delft
Hello James_D even if it is disabled i can select it with arrow keys... i am trying with row.editableProperty().bind(row.disabledProperty()) and row.focusTraversableProperty().bind(row.disabledProperty()) but i have no luck. What to do to make it completely unselectable and unfocusable :)Endamage
@Endamage To make it not selectable, you need to define a custom SelectionModel, which is a bit of a painDelft
Should i make a stand alone question for that? :)Endamage
@Endamage pretty sure I answered a question like that once :). But yes, it's different to this question, which is about disabling the row, not making it unselectable.Delft
#41204339Endamage

© 2022 - 2024 — McMap. All rights reserved.