javafx - 与自定义 CellFactory 节点交互将行添加到 TableView 选择列表中?

标签 javafx

我有一个带有 CellFactoryTableView,它将 ComboBox 放入其中一列中。 TableView 已启用 SelectionMode.MULTIPLE,但它与 ComboBox 单元格的行为很奇怪。

当用户单击ComboBox 选择一个值时,该行将添加到所选行的列表中。相反,单击 ComboBox 应该选择该行并取消选择所有其他行(除非按住 CTRL),或者根本不应该选择该行,而只允许与 交互组合框

我不知道如何实现这一目标。

这是一个演示该问题的完整示例:

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;

enum Manufacturer {
    HP, DELL, LENOVO, ASUS, ACER;
}

public class TableViewSelectionIssue extends Application {

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

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        // Simple TableView
        TableView<ComputerPart> tableView = new TableView<>();
        TableColumn<ComputerPart, Manufacturer> colManufacturer = new TableColumn<>("Manufacturer");
        TableColumn<ComputerPart, String> colItem = new TableColumn<>("Item");
        tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        colManufacturer.setCellValueFactory(t -> t.getValue().manufacturerProperty());
        colItem.setCellValueFactory(t -> t.getValue().itemNameProperty());

        tableView.getColumns().addAll(colManufacturer, colItem);

        // CellFactory to display ComboBox in colManufacturer
        colManufacturer.setCellFactory(param -> new ManufacturerTableCell(colManufacturer, FXCollections.observableArrayList(Manufacturer.values())));

        // Add sample items
        tableView.getItems().addAll(
                new ComputerPart("Keyboard"),
                new ComputerPart("Mouse"),
                new ComputerPart("Monitor"),
                new ComputerPart("Motherboard"),
                new ComputerPart("Hard Drive")
        );

        root.getChildren().add(tableView);

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }
}

class ComputerPart {

    private final ObjectProperty<Manufacturer> manufacturer = new SimpleObjectProperty<>();
    private final StringProperty itemName = new SimpleStringProperty();

    public ComputerPart(String itemName) {
        this.itemName.set(itemName);
    }

    public Manufacturer getManufacturer() {
        return manufacturer.get();
    }

    public void setManufacturer(Manufacturer manufacturer) {
        this.manufacturer.set(manufacturer);
    }

    public ObjectProperty<Manufacturer> manufacturerProperty() {
        return manufacturer;
    }

    public String getItemName() {
        return itemName.get();
    }

    public void setItemName(String itemName) {
        this.itemName.set(itemName);
    }

    public StringProperty itemNameProperty() {
        return itemName;
    }
}

class ManufacturerTableCell extends TableCell<ComputerPart, Manufacturer> {
    private final ComboBox<Manufacturer> cboStatus;

    ManufacturerTableCell(TableColumn<ComputerPart, Manufacturer> column, ObservableList<Manufacturer> items) {
        this.cboStatus = new ComboBox<>();
        this.cboStatus.setItems(items);
        this.cboStatus.setConverter(new StringConverter<Manufacturer>() {
            @Override
            public String toString(Manufacturer object) {
                return object.name();
            }

            @Override
            public Manufacturer fromString(String string) {
                return null;
            }
        });

        this.cboStatus.disableProperty().bind(column.editableProperty().not());
        this.cboStatus.setOnShowing(event -> {
            final TableView<ComputerPart> tableView = getTableView();
            tableView.getSelectionModel().select(getTableRow().getIndex());
            tableView.edit(tableView.getSelectionModel().getSelectedIndex(), column);
        });

        this.cboStatus.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (isEditing()) {
                commitEdit(newValue);
                column.getTableView().refresh();

            }
        });
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

    }

    @Override
    protected void updateItem(Manufacturer item, boolean empty) {
        super.updateItem(item, empty);

        setText(null);
        if (empty) {
            setGraphic(null);
        } else {
            this.cboStatus.setValue(item);
            this.setGraphic(this.cboStatus);
        }
    }
}

该示例以可预测的 UI 开始:

screenshot 1

但是,当与 Manufacturer 列中的 ComboBox 交互时,会选择相应的行。这是第一行的预期,但在与另一个 ComboBox 交互时不会取消选择。

screenshot 2


如何防止与 ComboBox 的后续交互添加到所选行?它的行为应该像 TableRow 上的任何其他点击一样,不是吗?

我正在使用 JDK 8u161。


Note: I understand there is a ComboBoxTableCell class available, but I've not been able to find any examples of how to use one properly; that is irrelevant to my question, though, unless the ComboBoxTableCell behaves differently.

最佳答案

由于您想要一个“始终编辑”的单元格,因此您的实现的行为应该更像 CheckBoxTableCell 而不是 ComboBoxTableCell。前者绕过了TableView的正常编辑机制。作为猜测,我认为是您对正常编辑机制的使用导致了选择问题 - 我不确定到底为什么。

将您的 ManufactureTableCell 修改为更像 CheckBoxTableCell,它看起来像:

class ManufacturerTableCell extends TableCell<ComputerPart, Manufacturer> {
    private final ComboBox<Manufacturer> cboStatus;
    private final IntFunction<Property<Manufacturer>> extractor;

    private Property<Manufacturer> property;


    ManufacturerTableCell(IntFunction<Property<Manufacturer>> extractor, ObservableList<Manufacturer> items) {
        this.extractor = extractor;

        this.cboStatus = new ComboBox<>();
        this.cboStatus.setItems(items);
        // removed StringConverter for brevity (accidentally)
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

        cboStatus.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
            if (event.isShortcutDown()) {
                getTableView().getSelectionModel().select(getIndex(), getTableColumn());
            } else {
                getTableView().getSelectionModel().clearAndSelect(getIndex(), getTableColumn());
            }
            event.consume();
        });
    }

    @Override
    protected void updateItem(Manufacturer item, boolean empty) {
        super.updateItem(item, empty);

        setText(null);
        clearProperty();
        if (empty) {
            setGraphic(null);
        } else {
            property = extractor.apply(getIndex());
            Bindings.bindBidirectional(cboStatus.valueProperty(), property);
            setGraphic(cboStatus);
        }
    }

    private void clearProperty() {
        setGraphic(null);
        if (property != null) {
            Bindings.unbindBidirectional(cboStatus.valueProperty(), property);
        }
    }
}

你可以像这样安装它:

// note you could probably share the same ObservableList between all cells
colManufacturer.setCellFactory(param ->
    new ManufacturerTableCell(i -> tableView.getItems().get(i).manufacturerProperty(),
            FXCollections.observableArrayList(Manufacturer.values())));

正如已经提到的,上述实现绕过了正常的编辑机制;它将 ComboBox 的值直接与模型项的属性联系起来。该实现还向 ComboBox 添加了一个 MOUSE_PRESSED 处理程序,用于根据需要选择行(或单元格,如果使用单元格选择)。不幸的是,我不太明白如何在 Shift 按下时实现选择,因此仅处理“按”和“快捷键+按”。

上面的内容按照我相信的方式工作,但我只能使用 JavaFX 12 进行测试。

关于javafx - 与自定义 CellFactory 节点交互将行添加到 TableView 选择列表中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55599013/

相关文章:

JavaFX : Not on FX application thread error occurs

java - FXML 文件无法打开

multithreading - JavaFX TableView 更新频繁

java - Text.setText() 不更新文本,抛出 NullPointerException

JavaFX 屏幕分辨率缩放

JavaFX 自定义 CellFactory 包装器抛出空指针

java - 当我通过单击窗口的 [X] 红色按钮关闭屏幕时会调用哪个事件?

java - 在同一场景的另一个 JavaFX Controller 中进行更改

JavaFx 空组合框

TextFlow 中的 JavaFX 文本忽略 StyleClass?