javafx:一次切换多个 CheckBoxTableCell-Checkboxes
javafx: Toggle multiple CheckBoxTableCell-Checkboxes at once
我的目标
我有一个带有布尔列的 editable table,可以在其中选中或取消选中包含的复选框和多行 selection 策略。我希望我的 table 在我选中或取消选中一个时自动切换所有 selected 行的所有复选框。
不知道你是否明白我的意思:D 我的意思是:
- 我select多行
- 我选中或取消选中那些 selected 行之一的复选框
- 所有其他行的复选框会自动选中或取消选中
现在应该清楚了;)
我的问题
(我是 JavaFX 的新手!我已经用 AWT/SWING 完成了我要求的同样的事情,但我无法让它与 JavaFX 一起工作)
JavaFX 中是否已经内置了类似的功能?如果不是,实现我的目标的最佳方法是什么?
到目前为止我做了什么
我发现,您可以通过将 CheckBoxTableCell-Callback 设置为所需列的 CellFactory 来侦听更改事件。我是这样做的:
TableColumn<FileSelection, Boolean> selectedColumn = new TableColumn<>("Sel");
selectedColumn.setCellValueFactory(new PropertyValueFactory<>("selected"));
selectedColumn.setCellFactory(CheckBoxTableCell.forTableColumn(rowidx -> {
if (tblVideoFiles.getSelectionModel().isSelected(rowidx)) {
tblVideoFiles.getSelectionModel().getSelectedItems().forEach(item -> {
if (!item.getFile().equals(tblVideoFiles.getItems().get(rowidx).getFile())) {
item.selectedProperty().set(!item.selectedProperty().get());
}
});
}
return fileList.get(rowidx).selectedProperty();
}));
这里的问题:一旦一个复选框被更改,它就会自动切换,导致一个切换循环来检查和取消选中它自己:D
我该如何阻止它?
我认为这最好直接通过 model/selection 模型完成,而不是通过细胞工厂。这是一种方法。基本思路是:
- 创建一个可观察列表,其中有一个 提取器 映射到 table 项目的 属性 表示该项目是否被选中(在table)
中复选框的意义
- 对 table 的选定项目使用侦听器以确保在步骤 1 中创建的列表始终包含在 table
中选定的项目
- 向步骤 1 中创建的列表添加一个侦听器以侦听更新;即表示是否通过复选框检查项目的属性的变化。
- 如果 table-selected 项目的选中 属性 发生变化,请更新所有选中项目的选中属性以匹配。设置一个标志,确保监听器忽略这些更改。
这是一个简单的例子。首先是一个简单的 table 模型 class:
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Item {
private final StringProperty name = new SimpleStringProperty();
private final BooleanProperty selected = new SimpleBooleanProperty();
public Item(String name) {
setName(name);
setSelected(false);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final BooleanProperty selectedProperty() {
return this.selected;
}
public final boolean isSelected() {
return this.selectedProperty().get();
}
public final void setSelected(final boolean selected) {
this.selectedProperty().set(selected);
}
}
然后是示例应用程序:
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
TableColumn<Item, String> itemCol = new TableColumn<>("Item");
itemCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
table.getColumns().add(itemCol);
TableColumn<Item, Boolean> selectedCol = new TableColumn<>("Select");
selectedCol.setCellValueFactory(cellData -> cellData.getValue().selectedProperty());
selectedCol.setCellFactory(CheckBoxTableCell.forTableColumn(selectedCol));
table.getColumns().add(selectedCol);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// observable list of items that fires updates when the selectedProperty of
// any item in the list changes:
ObservableList<Item> selectionList = FXCollections
.observableArrayList(item -> new Observable[] { item.selectedProperty() });
// bind contents to items selected in the table:
table.getSelectionModel().getSelectedItems().addListener(
(Change<? extends Item> c) -> selectionList.setAll(table.getSelectionModel().getSelectedItems()));
// add listener so that any updates in the selection list are propagated to all
// elements:
selectionList.addListener(new ListChangeListener<Item>() {
private boolean processingChange = false;
@Override
public void onChanged(Change<? extends Item> c) {
if (!processingChange) {
while (c.next()) {
if (c.wasUpdated() && c.getTo() - c.getFrom() == 1) {
boolean selectedVal = c.getList().get(c.getFrom()).isSelected();
processingChange = true;
table.getSelectionModel().getSelectedItems()
.forEach(item -> item.setSelected(selectedVal));
processingChange = false;
}
}
}
}
});
for (int i = 1; i <= 20; i++) {
table.getItems().add(new Item("Item " + i));
}
Scene scene = new Scene(new BorderPane(table));
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
请注意,有一个(烦人的)规则,即在处理更改时(例如,由侦听器)不应更改可观察列表。我不确定这是否完全遵守该规则,因为它更改了作为提取器一部分的可观察列表的属性,而侦听器正在处理这些更改。我 认为 该规则仅适用于 adding/removing 元素,不适用于更新它们,并且此代码似乎有效。但是,您可能需要 hack-around 将更新所选项目的代码包装在 Platform.runLater(...)
中以确保符合此规则:
@Override
public void onChanged(Change<? extends Item> c) {
if (!processingChange) {
while (c.next()) {
if (c.wasUpdated() && c.getTo() - c.getFrom() == 1) {
boolean selectedVal = c.getList().get(c.getFrom()).isSelected();
Platform.runLater(() -> {
processingChange = true;
table.getSelectionModel().getSelectedItems()
.forEach(item -> item.setSelected(selectedVal));
processingChange = false;
});
}
}
}
}
如 所述,保留 selectedItems 的副本并同步两者(在 listChange/Listener api 的限制下)的替代方法是将责任转移到CheckBoxTableCell 的自定义扩展的复选框的操作处理程序。
这也不是没有麻烦,因为复选框本身不能直接访问。这可以通过一个小技巧来克服(注意:使用实现知识,因为我们希望复选框作为单元格的图形!):听取单元格的图形 属性 并在其值为第一次时注册操作不为空(并且 de-register 又是 属性 侦听器)。
下面是有关如何制作此类自定义单元格的示例re-usable:
- 该单元配置了一个消费者(在 TableCell 上参数化),该消费者根据 passed-in 单元的状态执行实际工作负载
- 在实例化时,单元格围绕消费者包装一个动作处理程序:动作只是向消费者发送消息,将单元格作为参数传递
- (诀窍:在图形第一次失效时 属性,操作处理程序设置为复选框)
此处实现的行为与 James 的解决方案不同,因为如果选定行中的复选框触发(而不是在以编程方式更改 属性).使用哪个取决于需求。
自定义单元格:
public class ActionableCheckBoxTableCell<S, T> extends CheckBoxTableCell<S, T> {
EventHandler<ActionEvent> action;
InvalidationListener installer;
public ActionableCheckBoxTableCell(Consumer<TableCell<S, T>> cellConsumer) {
action = ev -> {
cellConsumer.accept(this);
};
registerActionInstaller();
}
/**
* Trick to set the action on checkBox (private in super).
*/
protected void registerActionInstaller() {
installer = ov -> {
if (getGraphic() != null) {
((ButtonBase) getGraphic()).setOnAction(action);
graphicProperty().removeListener(installer);
}
};
graphicProperty().addListener(installer);
}
}
使用示例(劫持 James 的解决方案 - 只需通过设置自定义单元工厂而不是核心单元工厂来替换列表布线):
Consumer<TableCell<Item, Boolean>> cellConsumer = cell -> {
TableView<Item> tv = cell.getTableView();
Item rowItem = tv.getItems().get(cell.getIndex());
if (!tv.getSelectionModel().getSelectedItems().contains(rowItem)) return;
tv.getSelectionModel().getSelectedItems().forEach(item -> item.setSelected(rowItem.isSelected()));
};
selectedCol.setCellFactory(cc -> new ActionableCheckBoxTableCell<>(cellConsumer));
我的目标
我有一个带有布尔列的 editable table,可以在其中选中或取消选中包含的复选框和多行 selection 策略。我希望我的 table 在我选中或取消选中一个时自动切换所有 selected 行的所有复选框。 不知道你是否明白我的意思:D 我的意思是:
- 我select多行
- 我选中或取消选中那些 selected 行之一的复选框
- 所有其他行的复选框会自动选中或取消选中
现在应该清楚了;)
我的问题
(我是 JavaFX 的新手!我已经用 AWT/SWING 完成了我要求的同样的事情,但我无法让它与 JavaFX 一起工作)
JavaFX 中是否已经内置了类似的功能?如果不是,实现我的目标的最佳方法是什么?
到目前为止我做了什么
我发现,您可以通过将 CheckBoxTableCell-Callback 设置为所需列的 CellFactory 来侦听更改事件。我是这样做的:
TableColumn<FileSelection, Boolean> selectedColumn = new TableColumn<>("Sel");
selectedColumn.setCellValueFactory(new PropertyValueFactory<>("selected"));
selectedColumn.setCellFactory(CheckBoxTableCell.forTableColumn(rowidx -> {
if (tblVideoFiles.getSelectionModel().isSelected(rowidx)) {
tblVideoFiles.getSelectionModel().getSelectedItems().forEach(item -> {
if (!item.getFile().equals(tblVideoFiles.getItems().get(rowidx).getFile())) {
item.selectedProperty().set(!item.selectedProperty().get());
}
});
}
return fileList.get(rowidx).selectedProperty();
}));
这里的问题:一旦一个复选框被更改,它就会自动切换,导致一个切换循环来检查和取消选中它自己:D 我该如何阻止它?
我认为这最好直接通过 model/selection 模型完成,而不是通过细胞工厂。这是一种方法。基本思路是:
- 创建一个可观察列表,其中有一个 提取器 映射到 table 项目的 属性 表示该项目是否被选中(在table) 中复选框的意义
- 对 table 的选定项目使用侦听器以确保在步骤 1 中创建的列表始终包含在 table 中选定的项目
- 向步骤 1 中创建的列表添加一个侦听器以侦听更新;即表示是否通过复选框检查项目的属性的变化。
- 如果 table-selected 项目的选中 属性 发生变化,请更新所有选中项目的选中属性以匹配。设置一个标志,确保监听器忽略这些更改。
这是一个简单的例子。首先是一个简单的 table 模型 class:
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Item {
private final StringProperty name = new SimpleStringProperty();
private final BooleanProperty selected = new SimpleBooleanProperty();
public Item(String name) {
setName(name);
setSelected(false);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final BooleanProperty selectedProperty() {
return this.selected;
}
public final boolean isSelected() {
return this.selectedProperty().get();
}
public final void setSelected(final boolean selected) {
this.selectedProperty().set(selected);
}
}
然后是示例应用程序:
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
TableColumn<Item, String> itemCol = new TableColumn<>("Item");
itemCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
table.getColumns().add(itemCol);
TableColumn<Item, Boolean> selectedCol = new TableColumn<>("Select");
selectedCol.setCellValueFactory(cellData -> cellData.getValue().selectedProperty());
selectedCol.setCellFactory(CheckBoxTableCell.forTableColumn(selectedCol));
table.getColumns().add(selectedCol);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// observable list of items that fires updates when the selectedProperty of
// any item in the list changes:
ObservableList<Item> selectionList = FXCollections
.observableArrayList(item -> new Observable[] { item.selectedProperty() });
// bind contents to items selected in the table:
table.getSelectionModel().getSelectedItems().addListener(
(Change<? extends Item> c) -> selectionList.setAll(table.getSelectionModel().getSelectedItems()));
// add listener so that any updates in the selection list are propagated to all
// elements:
selectionList.addListener(new ListChangeListener<Item>() {
private boolean processingChange = false;
@Override
public void onChanged(Change<? extends Item> c) {
if (!processingChange) {
while (c.next()) {
if (c.wasUpdated() && c.getTo() - c.getFrom() == 1) {
boolean selectedVal = c.getList().get(c.getFrom()).isSelected();
processingChange = true;
table.getSelectionModel().getSelectedItems()
.forEach(item -> item.setSelected(selectedVal));
processingChange = false;
}
}
}
}
});
for (int i = 1; i <= 20; i++) {
table.getItems().add(new Item("Item " + i));
}
Scene scene = new Scene(new BorderPane(table));
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
请注意,有一个(烦人的)规则,即在处理更改时(例如,由侦听器)不应更改可观察列表。我不确定这是否完全遵守该规则,因为它更改了作为提取器一部分的可观察列表的属性,而侦听器正在处理这些更改。我 认为 该规则仅适用于 adding/removing 元素,不适用于更新它们,并且此代码似乎有效。但是,您可能需要 hack-around 将更新所选项目的代码包装在 Platform.runLater(...)
中以确保符合此规则:
@Override
public void onChanged(Change<? extends Item> c) {
if (!processingChange) {
while (c.next()) {
if (c.wasUpdated() && c.getTo() - c.getFrom() == 1) {
boolean selectedVal = c.getList().get(c.getFrom()).isSelected();
Platform.runLater(() -> {
processingChange = true;
table.getSelectionModel().getSelectedItems()
.forEach(item -> item.setSelected(selectedVal));
processingChange = false;
});
}
}
}
}
如
这也不是没有麻烦,因为复选框本身不能直接访问。这可以通过一个小技巧来克服(注意:使用实现知识,因为我们希望复选框作为单元格的图形!):听取单元格的图形 属性 并在其值为第一次时注册操作不为空(并且 de-register 又是 属性 侦听器)。
下面是有关如何制作此类自定义单元格的示例re-usable:
- 该单元配置了一个消费者(在 TableCell 上参数化),该消费者根据 passed-in 单元的状态执行实际工作负载
- 在实例化时,单元格围绕消费者包装一个动作处理程序:动作只是向消费者发送消息,将单元格作为参数传递
- (诀窍:在图形第一次失效时 属性,操作处理程序设置为复选框)
此处实现的行为与 James 的解决方案不同,因为如果选定行中的复选框触发(而不是在以编程方式更改 属性).使用哪个取决于需求。
自定义单元格:
public class ActionableCheckBoxTableCell<S, T> extends CheckBoxTableCell<S, T> {
EventHandler<ActionEvent> action;
InvalidationListener installer;
public ActionableCheckBoxTableCell(Consumer<TableCell<S, T>> cellConsumer) {
action = ev -> {
cellConsumer.accept(this);
};
registerActionInstaller();
}
/**
* Trick to set the action on checkBox (private in super).
*/
protected void registerActionInstaller() {
installer = ov -> {
if (getGraphic() != null) {
((ButtonBase) getGraphic()).setOnAction(action);
graphicProperty().removeListener(installer);
}
};
graphicProperty().addListener(installer);
}
}
使用示例(劫持 James 的解决方案 - 只需通过设置自定义单元工厂而不是核心单元工厂来替换列表布线):
Consumer<TableCell<Item, Boolean>> cellConsumer = cell -> {
TableView<Item> tv = cell.getTableView();
Item rowItem = tv.getItems().get(cell.getIndex());
if (!tv.getSelectionModel().getSelectedItems().contains(rowItem)) return;
tv.getSelectionModel().getSelectedItems().forEach(item -> item.setSelected(rowItem.isSelected()));
};
selectedCol.setCellFactory(cc -> new ActionableCheckBoxTableCell<>(cellConsumer));