javafx:一次切换多个CheckBoxTableCell-Checkbox

如何解决javafx:一次切换多个CheckBoxTableCell-Checkbox

我的目标

我有一个带有布尔列的可编辑表,在其中可以选中或取消选中包含的复选框和多行选择策略。我希望我的表格在我选中或取消选中某个复选框后立即自动切换所有选定行的所有复选框。 不知道你是否明白这一点:D我的意思是:

  1. 我选择了多行
  2. 我选中或取消选中那些选定行之一的复选框
  3. 其他所有行的复选框都会自动选中或取消选中

现在应该清楚了;)

我的问题

(我是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 我该如何阻止呢?

解决方法

我认为最好直接通过模型/选择模型来完成,而不是通过单元工厂来完成。这是一种方法。基本思想是:

  1. 创建一个具有 extractor 到表项属性的映射的可观察列表,表示是否已选中该项(在表中的复选框中)
  2. 在表的选定项目上使用侦听器,以确保在步骤1中创建的列表始终包含表中选定的项目
  3. 将侦听器添加到步骤1中创建的侦听更新的列表中;即更改表示是否通过复选框选中项目的属性。
  4. 如果表选定项的选中属性发生更改,请更新所有选定项的选中属性以进行匹配。设置一个标志,以确保侦听器将忽略这些更改。

这是一个简单的例子。首先是一个简单的表模型类:

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();
    }

}

请注意,有一个(讨厌的)规则,即在处理更改(例如,由侦听器)时,不应更改可观察列表。我不确定这是否完全遵守该规则,因为它更改了作为提取器一部分的可观察列表的属性,而侦听器正在处理这些更改。我认为该规则仅适用于添加/删除元素,而不适用于更新它们,并且此代码似乎有效。但是,您可能希望采用一种黑客手段,将用于更新所选项目的代码包装在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;
                        });
                    }
                }
            }
        }
,

James' solution中所述,除了保留selectedItems的副本并使两者同步(在listChange / Listener api的约束下)的一种替代方法是将职责移至自定义复选框的动作处理程序中CheckBoxTableCell的扩展。

那也不是没有麻烦,因为checkBox本身不能直接访问。这可以通过一点技巧来克服(请注意:使用实现知识,因为我们期望checkBox作为单元格的图形!):侦听单元格的graphic属性,并在第一次其值不为null时注册该操作(并且再次取消注册属性侦听器。)

下面是有关如何使这种自​​定义单元可重复使用的示例:

  • 该单元配置有一个使用者(在TableCell上参数化),该使用者根据传入单元的状态执行实际的工作量
  • 在实例化时,单元将动作处理程序包装在使用者周围:该动作只是向消费者发送消息,并将单元作为参数传递
  • (技巧:在图形属性第一次失效时,将动作处理程序设置为复选框)

此处实现的行为与James的解决方案不同之处在于,它仅在激发选定行中的复选框时(而不是通过编程方式更改该属性时)才更新其他selectedItems的该项selected属性。使用哪个取决于要求。

自定义单元格:

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));

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-