Make individual cell editable in JavaFX tableview

2020-02-12 12:30发布

问题:

I am writing a program that displays a JavaFX table. I understand how to make all the data in a specific column editable via "Column.setCellFactory(TextFieldTableCell.forTableColumn());"

However I would like to make some of the cells editable and others immutable. Is this possible? Moreover, I would like editable cells to either have a border or have a unique font color.

回答1:

Yes, this is possible: the TableCell has an editable property inherited from the Cell class. You need to arrange that the cell sets its editable property accordingly when the item changes (and possibly if the condition governing when it should be editable changes).

In the example below, I create a default cell factory using TextFieldTableCell.forTableColumn(), and then create another cell factory. The custom cell factory invokes the default cell factory (to get the standard TextField behavior), then observes the itemProperty of the cell and updates the editableProperty accordingly (in this simple example, only cells with an even value are editable).

To add the border, you need to update the style somehow. The best way to do this is to define a pseudoclass for "editable" and use an external style sheet to manage the style for editable cells.

import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class ConditionallyEditableTableCell extends Application {

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

        table.setEditable(true);

        TableColumn<Item, String> nameCol = createCol("Name", Item::nameProperty);
        TableColumn<Item, Number> canEditCol = createCol("Value", Item::valueProperty);

        PseudoClass editableCssClass = PseudoClass.getPseudoClass("editable");

        Callback<TableColumn<Item, String>, TableCell<Item, String>> defaultTextFieldCellFactory 
            = TextFieldTableCell.<Item>forTableColumn();

        nameCol.setCellFactory(col -> {
            TableCell<Item, String> cell = defaultTextFieldCellFactory.call(col);
            cell.itemProperty().addListener((obs, oldValue, newValue) -> {
                TableRow row = cell.getTableRow();
                if (row == null) {
                    cell.setEditable(false);
                } else {
                    Item item = (Item) cell.getTableRow().getItem();
                    if (item == null) {
                        cell.setEditable(false);
                    } else {
                        cell.setEditable(item.getValue() % 2 == 0);
                    }
                }
                cell.pseudoClassStateChanged(editableCssClass, cell.isEditable());
            });
            return cell ;
        });

        table.getColumns().addAll(canEditCol, nameCol);

        for (int i=1; i<=20; i++) {
            table.getItems().add(new Item("Item "+i, i));
        }

        Scene scene = new Scene(new BorderPane(table), 600, 400);

        scene.getStylesheets().add("conditionally-editable-table-cell.css");

        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

    public static class Item {
        private final IntegerProperty value = new SimpleIntegerProperty();
        private final StringProperty name = new SimpleStringProperty();


        public Item(String name, int value) {
            setName(name);
            setValue(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 static void main(String[] args) {
        launch(args);
    }
}

And the stylesheet, conditionally-editable-table-cell.css:

.table-cell:editable {
    -fx-border-color: red ;
}