我有一个带有 CellFactory
的 TableView
,它将 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 开始:
但是,当与 Manufacturer 列中的 ComboBox
交互时,会选择相应的行。这是第一行的预期,但在与另一个 ComboBox
交互时不会取消选择。
如何防止与 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 theComboBoxTableCell
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/