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

时间:2019-04-09 18:23:20

标签: javafx

我有一个TableView和一个CellFactory,将ComboBox放在其中一列中。 TableView启用了SelectionMode.MULTIPLE,但在ComboBox单元中却表现得很奇怪。

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

我不确定如何实现这一目标。

下面是一个完整的示例来演示此问题:

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

但是,当与制造商列中的ComboBox进行交互时,将选择相应的行。这是第一行所期望的,但是在与另一个ComboBox进行交互时不会被取消选择。

screenshot 2


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

我正在使用JDK 8u161。


  

注意: 我知道有一个可用的 ComboBoxTableCell 类,但是我找不到任何使用方法的示例适当地但这与我的问题无关,除非 ComboBoxTableCell 的行为不同。

1 个答案:

答案 0 :(得分:2)

由于您需要一个“始终在编辑”的单元格,因此您的实现应更像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的值直接关联到模型项的属性。该实现还向MOUSE_PRESSED添加了ComboBox处理程序,该处理程序将根据需要选择行(如果使用单元格选择,则选择单元格)。不幸的是,当 Shift 按下时,我不太了解如何实现选择,因此仅处理“ Press”和“ Shortcut + Press”。

上面的代码可以满足您的要求,但是我只能使用JavaFX 12进行测试。