Javafx 与自定义CellFactory节点交互是否将行添加到TableView选择列表?

Javafx 与自定义CellFactory节点交互是否将行添加到TableView选择列表?,javafx,Javafx,我有一个TableView和一个CellFactory,它将一个组合框放在其中一列中。TableView具有SelectionMode。启用了多个,但它在组合框单元格中的行为异常 当用户单击组合框选择一个值时,该行将添加到所选行的列表中。相反,单击组合框应该选择该行并取消选择所有其他行(除非按住CTRL键),或者根本不选择该行,而只允许与组合框交互 我不知道如何做到这一点 下面是一个完整的示例来演示该问题: import javafx.application.Application; impor

我有一个
TableView
和一个
CellFactory
,它将一个
组合框
放在其中一列中。
TableView
具有
SelectionMode。启用了多个
,但它在组合框单元格中的行为异常

当用户单击组合框选择一个值时,该行将添加到所选行的列表中。相反,单击
组合框
应该选择该行并取消选择所有其他行(除非按住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);
        }
    }
}
导入javafx.application.application;
导入javafx.beans.property.ObjectProperty;
导入javafx.beans.property.SimpleObject属性;
导入javafx.beans.property.SimpleStringProperty;
导入javafx.beans.property.StringProperty;
导入javafx.collections.FXCollections;
导入javafx.collections.ObservableList;
导入javafx.geometry.Insets;
导入javafx.geometry.Pos;
导入javafx.scene.scene;
导入javafx.scene.control.*;
导入javafx.scene.layout.VBox;
导入javafx.stage.stage;
导入javafx.util.StringConverter;
枚举制造商{
惠普、戴尔、联想、华硕、宏碁;
}
公共类TableViewSelectionSue扩展了应用程序{
公共静态void main(字符串[]args){
发射(args);
}
@凌驾
公共无效开始(阶段primaryStage){
//简单接口
VBox根=新的VBox(10);
根部设置对齐(位置中心);
根。设置填充(新插图(10));
//简单桌面视图
TableView TableView=新建TableView();
TableColumn colManufacturer=新的TableColumn(“制造商”);
TableColumn colItem=新的TableColumn(“项目”);
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
colManufacturer.setCellValueFactory(t->t.getValue().manufacturerProperty());
colItem.setCellValueFactory(t->t.getValue().itemNameProperty());
tableView.getColumns().addAll(colManufacturer,colItem);
//CellFactory在colManufacturer中显示组合框
colManufacturer.setCellFactory(参数->新建ManufacturerTableCell(colManufacturer,FXCollections.observableArrayList(Manufacturer.values()));
//添加示例项
tableView.getItems().addAll(
新电脑零件(“键盘”),
新电脑零件(“鼠标”),
新电脑零件(“显示器”),
新计算机部件(“主板”),
新计算机部件(“硬盘驱动器”)
);
root.getChildren().add(tableView);
//上台
primaryStage.setScene(新场景(根));
初级阶段。设置标题(“样本”);
primaryStage.show();
}
}
类计算机部件{
私有最终ObjectProperty制造商=新的SimpleObjectProperty();
private final StringProperty itemName=新SimpleStringProperty();
公共计算机部件(字符串项名称){
this.itemName.set(itemName);
}
公共制造商getManufacturer(){
返回manufacturer.get();
}
公共制造商(制造商){
此.manufacturer.set(制造商);
}
公共对象属性manufacturerProperty(){
退货制造商;
}
公共字符串getItemName(){
返回itemName.get();
}
public void setItemName(字符串itemName){
this.itemName.set(itemName);
}
公共StringProperty itemNameProperty(){
返回itemName;
}
}
类ManufacturerTableCell扩展了TableCell{
专用最终组合框cboStatus;
ManufacturerTableCell(表列、可观察列表项){
this.cboStatus=新组合框();
此.cboStatus.setItems(项目);
this.cboStatus.setConverter(新的StringConverter(){
@凌驾
公共字符串toString(制造商对象){
返回object.name();
}
@凌驾
公共制造商fromString(字符串){
返回null;
}
});
this.cboStatus.disableProperty().bind(column.editableProperty().not());
this.cboStatus.setOnShowing(事件->{
final TableView TableView=getTableView();
tableView.getSelectionModel().select(getTableRow().getIndex());
编辑(tableView.getSelectionModel().getSelectedIndex(),列);
});
this.cboStatus.valueProperty().addListener((可观察、旧值、新值)->{
if(isEditing()){
承诺IT(新价值);
column.getTableView().refresh();
}
});
setContentDisplay(仅限ContentDisplay.GRAPHIC_);
}
@凌驾
受保护的无效更新项(制造商项,布尔值为空){
super.updateItem(项,空);
setText(空);
if(空){
设置图形(空);
}否则{
此.cboStatus.setValue(项);
这个.setGraphic(这个.cboStatus);
}
}
}

该示例从一个可预测的UI开始:

但是,当与制造商列中的
组合框
交互时,会选择相应的行。第一行需要这样做,但与另一个
组合框
交互时不会取消选择


如何防止与组合框的后续交互
添加到所选行?它的行为应该类似于在
表格行上的任何其他单击,不是吗

我使用的是JDK 8u161


注意:我知道有一个
ComboBoxTableCell
类可用,但我还没有找到任何关于如何使用一个道具的示例
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())));