我有一个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开头:
但是,当与制造商列中的ComboBox
进行交互时,将选择相应的行。这是第一行所期望的,但是在与另一个ComboBox
进行交互时不会被取消选择。
如何防止与ComboBox
的后续交互添加到所选行?它的行为应该与对TableRow
的任何其他单击一样,不是吗?
我正在使用JDK 8u161。
注意: 我知道有一个可用的
ComboBoxTableCell
类,但是我找不到任何使用方法的示例适当地但这与我的问题无关,除非ComboBoxTableCell
的行为不同。
答案 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进行测试。