Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX: How to disable a row in a TableView?

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.

like image 673
bashoogzaad Avatar asked Oct 28 '14 11:10

bashoogzaad


People also ask

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

What does TableView refresh do?

Calling refresh() forces the TableView control to recreate and repopulate the cells necessary to populate the visual bounds of the control. In other words, this forces the TableView to update what it is showing to the user.

What is TableView in JavaFX?

The TableView class provides built-in capabilities to sort data in columns. Users can alter the order of data by clicking column headers. The first click enables the ascending sorting order, the second click enables descending sorting order, and the third click disables sorting. By default, no sorting is applied.


1 Answers

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.

like image 146
James_D Avatar answered Sep 17 '22 12:09

James_D