为 TableView 单个单元格设置禁用属性

Posted

技术标签:

【中文标题】为 TableView 单个单元格设置禁用属性【英文标题】:Set disable property for TableView individual cells 【发布时间】:2019-12-21 20:08:53 【问题描述】:

如何为 TableView 中的单个单元格设置禁用属性?

这是我的场景:一列有ComboBoxTableCell,根据选择的项目,同一行的某些单元格将被禁用。

例如:

如果我选择类型 A,则启用输入 A,禁用输入 B,反之亦然。

禁用是指已清除、变灰且不可编辑。

我的场景的一个最小示例:

TableTest.java

package minimalexample;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableTest extends Application 

    private TableView itemsTable;

    ObservableList<ItemsTableLine> items;

    @Override
    public void start(Stage primaryStage) 

        items = FXCollections.observableArrayList();
        items.addAll(new ItemsTableLine("A","1","2"),
                     new ItemsTableLine("A","3","4"),
                     new ItemsTableLine("B","5", "6"),
                     new ItemsTableLine());

        ItemsTable itemsTableParent = new ItemsTable();
        itemsTable = itemsTableParent.makeTable(items);

        VBox root = new VBox();        
        root.getChildren().addAll(itemsTable);

        Scene scene = new Scene(root, 200, 100);
        primaryStage.setTitle("Minimal Example");
        primaryStage.setScene(scene);
        primaryStage.show();
    


ItemsTable.java

package minimalexample;


import javafx.util.Callback;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Pos;

import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;

import javafx.collections.ObservableList;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public class ItemsTable 

    private String lastKey = null;

    // This will be exposed through a getter to be updated from list in LoadsTable
    private TableColumn<ItemsTableLine, ItemType> typeCol;
    private TableColumn<ItemsTableLine, String> inputACol;
    private TableColumn<ItemsTableLine, String> inputBCol;

    public TableView makeTable(ObservableList<ItemsTableLine> items) 

        TableView tv = new TableView(items);
        tv.setEditable(true);

        Callback<TableColumn<ItemsTableLine, String>, TableCell<ItemsTableLine, String>> txtCellFactory
                = (TableColumn<ItemsTableLine, String> p) -> 
                    return new EditingCell();
                ;

        ObservableList<ItemType> itemTypeList
                = FXCollections.observableArrayList(ItemType.values());
        typeCol = new TableColumn<>("Type");
        inputACol  = new TableColumn<>("Input A");
        inputBCol   = new TableColumn<>("Input B");

        typeCol.setCellValueFactory(new Callback<CellDataFeatures<ItemsTableLine, ItemType>, ObservableValue<ItemType>>() 

            @Override
            public ObservableValue<ItemType> call(CellDataFeatures<ItemsTableLine, ItemType> param) 
                ItemsTableLine lineItem = param.getValue();
                String itemTypeCode = lineItem.typeProperty().get();
                ItemType itemType = ItemType.getByCode(itemTypeCode);
                return new SimpleObjectProperty<>(itemType);
            
        );

        inputACol.setCellValueFactory(new PropertyValueFactory<>("inputA"));
        inputBCol.setCellValueFactory(new PropertyValueFactory<>("inputB"));
        typeCol.setCellFactory(ComboBoxTableCell.forTableColumn(itemTypeList));
        inputACol.setCellFactory(txtCellFactory);
        inputBCol.setCellFactory(txtCellFactory);
        typeCol.setOnEditCommit((CellEditEvent<ItemsTableLine, ItemType> event) -> 
            TablePosition<ItemsTableLine, ItemType> pos = event.getTablePosition();
            ItemType newItemType = event.getNewValue();
            int row = pos.getRow();
            ItemsTableLine lineItem = event.getTableView().getItems().get(row);
            lineItem.setType(newItemType.getCode());
        );
        inputACol.setOnEditCommit((TableColumn.CellEditEvent<ItemsTableLine, String> evt) -> 
            evt.getTableView().getItems().get(evt.getTablePosition().getRow())
                    .inputAProperty().setValue(evt.getNewValue().replace(",", "."));
        );
        inputBCol.setOnEditCommit((TableColumn.CellEditEvent<ItemsTableLine, String> evt) -> 
            evt.getTableView().getItems().get(evt.getTablePosition().getRow())
                    .inputBProperty().setValue(evt.getNewValue().replace(",", "."));
        );


        tv.getColumns().setAll(typeCol, inputACol, inputBCol);

        return tv;
    

    private class EditingCell extends TableCell 
        private TextField textField;
        @Override public void startEdit() 
            if (!isEmpty()) 
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                //setContentDisplay(ContentDisplay.GRAPHIC_ONLY); 
                Platform.runLater(() -> //without this space erases text, f2 doesn't
                    textField.requestFocus();//also selects
                );
                if (lastKey != null) 
                    textField.setText(lastKey);
                    Platform.runLater(() -> textField.deselect(); textField.end(););
            
        
        public void commit()  commitEdit(textField.getText()); 

        @Override
        public void cancelEdit() 
            super.cancelEdit();
            try 
                setText(getItem().toString());
             catch (Exception e) 
            
            setGraphic(null);
        

        @Override
        public void updateItem(Object item, boolean empty) 
            super.updateItem(item, empty);

            if (empty) 
                setText(null);
                setGraphic(null);
             else if (isEditing()) 
                if (textField != null) 
                    textField.setText(getString());
                
                setText(null);
                setGraphic(textField);
             else 
                setText(getString());
                setGraphic(null);
                if (!getTableColumn().getText().equals("Name")) 
                    setAlignment(Pos.CENTER);
                
            
        
        private void createTextField() 
            textField = new TextField(getString());
            textField.focusedProperty().addListener(
                    (ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) -> 
                        if (!arg2)  commitEdit(textField.getText()); );
            textField.setOnKeyReleased((KeyEvent t) -> 
                if (t.getCode() == KeyCode.ENTER) 
                    commitEdit(textField.getText());
                    EditingCell.this.getTableView().getSelectionModel().selectBelowCell(); 
                if (t.getCode() == KeyCode.ESCAPE)  cancelEdit(); );
            textField.addEventFilter(KeyEvent.KEY_RELEASED, (KeyEvent t) ->  
                if (t.getCode() == KeyCode.DELETE)  t.consume(););
        
        private String getString() return getItem() == null ? "" : getItem().toString();
    


ItemsTableLine.java

package minimalexample;
import java.io.Serializable;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class ItemsTableLine implements Serializable 

    private StringProperty type;
    private StringProperty inputA;
    private StringProperty inputB;

    public StringProperty typeProperty() return type;
    public StringProperty inputAProperty()  return inputA;  
    public StringProperty inputBProperty()   return inputB;

    public ItemsTableLine() 
        super();
        type = new SimpleStringProperty("");
        inputA  = new SimpleStringProperty("");
        inputB   = new SimpleStringProperty("");
    

    public ItemsTableLine(String...values) 
        this();
        type.set(values[0]);
        inputA.set(values[1]);
        inputB.set(values[2]);
    

    // Setters required due to combobox 
    public void setType(String value) 
        type.set(value);
    

ItemsType.java

package minimalexample;

public enum ItemType 
    TYPEA("A", "Type A"),
    TYPEB("B", "Type B");
    private String code;
    private String text;
    private ItemType(String code, String text)  this.code = code; this.text = text;
    public String getCode()  return code; 
    public String getText()  return text; 
    public static ItemType getByCode(String genderCode) 
       for (ItemType g : ItemType.values()) if (g.code.equals(genderCode)) return g;
       return null;
    
    @Override public String toString()  return this.text; 

【问题讨论】:

有点不太重要 ;) 想知道为什么您的模型将类型公开为 String(而不是 ItemType)?使用后者,您可以摆脱编辑处理程序,只需使用 valueFactories 中的属性。此外,您需要通过使用带有提取器的 observableList 在类型更改时触发项目。在其他列的单元格中,检查类型并根据需要启用/禁用。 在 Java 中很难有最小且有效的示例 =) 模型公开字符串的具体原因,而不是我在 java 和 javafx 方面的有限技能。如果您能对您的提议做出答复,以便我接受。 正如我所说:在不那么最小的方面;)请丢弃自定义编辑单元格和编辑处理程序(由于有 TextFieldTableCell,不需要前者,如果模型不需要后者设置正确) 实际上,这看起来像是一个建模问题:它应该是模型(或视图包装器)定义何时应该启用单元格,而不是单元格本身(应该尽可能简单,对逻辑没有责任,只是跟随其主人的奴隶)。从模型的角度来看,inputA 可以为 typeB 的 !=null 感觉是错误的...... hmm ...核心可能存在问题,无法通过快速检查使其正常工作..需要在本周晚些时候进行挖掘..sry ;) 【参考方案1】:

代码需要一些重构,但您可以通过执行以下操作来实现您的要求:

inputACol.setCellFactory(param -> new EditingCell() 
    @Override
    public void updateItem(Object item, boolean empty) 
        super.updateItem(item, empty);
        if (item != null && !empty) 
            ItemsTableLine data = (ItemsTableLine) getTableView().getItems().get(getIndex());
            disableProperty().bind(Bindings.createBooleanBinding(() ->
                    !data.typeProperty().get().equals("A"), data.typeProperty()));
         else 
            disableProperty().unbind();
        
    
);
inputBCol.setCellFactory(param -> new EditingCell() 
    @Override
    public void updateItem(Object item, boolean empty) 
        super.updateItem(item, empty);
        if (item != null && !empty) 
            ItemsTableLine data = (ItemsTableLine) getTableView().getItems().get(getIndex());
            disableProperty().bind(Bindings.createBooleanBinding(() ->
                    !data.typeProperty().get().equals("B"), data.typeProperty()));
         else 
            disableProperty().unbind();
        
    
);

注意:有不止一种方法可以做到这一点,但在你的情况下这似乎是最简单的。

注意:除了绑定中使用的值之外,两个代码块都是相同的,您可以编写一个将其作为输入并返回单元工厂而不是重复代码的方法。

【讨论】:

以上是关于为 TableView 单个单元格设置禁用属性的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 plist 中的数据阻止或禁用 Tableview 中的自定义单元格?

如何在iOS,Objective C的tableview中的每个部分中仅实现单个单元格选择

TableView - 选择单元格时的操作

仅将 IBAction 应用于单个单元格

Tableview 禁用单元格中的用户交互

ios:在点击collectionview单元格或tableview单元格时禁用第二个触摸事件