You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在双ListView选择系统中实现筛选并保持列表可编辑?

解决ListView筛选时条目移动的UnsupportedOperationException问题

我来帮你搞定这个问题!你遇到的UnsupportedOperationException主要有两个核心原因:

  • 错误地用FXCollections.observableList()包装了SortedList,这个方法返回的是不可修改的列表包装对象
  • 直接操作了ListView的过滤视图(lvAvailable.getItems()),而不是原始的数据源列表

核心原理

FilteredList和SortedList都是基于原始ObservableList的视图层,它们本身不存储数据,只是对原始数据进行过滤/排序后的展示载体。所有的数据增删操作都应该直接针对原始的availableList和selectedList,这样视图会自动同步更新,同时保证列表的可编辑性。

修改后的控制器代码

下面是修复后的完整Controller类,我标注了关键修改点:

public class Controller {
    @FXML private TextField txtSearch;
    @FXML private ListView<String> lvAvailable;
    @FXML private ListView<String> lvSelected;

    private ObservableList<String> availableList = FXCollections.observableArrayList();
    private ObservableList<String> selectedList = FXCollections.observableArrayList();

    @FXML private void initialize() {
        // 初始化可用列表数据
        availableList.addAll(
                "One", "Two", "Three", "Four", "Five"
        );

        // 1. 创建过滤列表,基于原始的availableList
        FilteredList<String> filteredList = new FilteredList<>(availableList);
        // 2. 创建排序列表,基于过滤列表
        SortedList<String> sortedList = new SortedList<>(filteredList);
        
        // 3. 直接把SortedList设置给ListView,不需要额外包装
        lvAvailable.setItems(sortedList);
        lvAvailable.setPlaceholder(new Label("No results"));

        // 搜索过滤逻辑保持不变
        txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
            filteredList.setPredicate(availableItem -> {
                if (newValue == null || newValue.isEmpty()) {
                    return true;
                }
                String query = newValue.toLowerCase();
                return availableItem.toLowerCase().contains(query);
            });
        });

        // 双击移动条目逻辑 - 修改为操作原始列表
        lvAvailable.setOnMouseClicked(mouseEvent -> {
            if (mouseEvent.getButton().equals(MouseButton.PRIMARY) && mouseEvent.getClickCount() == 2) {
                String selectedItem = lvAvailable.getSelectionModel().getSelectedItem();
                addItem(selectedItem);
            }
        });

        lvSelected.setOnMouseClicked(mouseEvent -> {
            if (mouseEvent.getButton().equals(MouseButton.PRIMARY) && mouseEvent.getClickCount() == 2) {
                String selectedItem = lvSelected.getSelectionModel().getSelectedItem();
                removeItem(selectedItem);
            }
        });

        // 把selectedList直接设置给已选ListView
        lvSelected.setItems(selectedList);
    }

    private void addItem(String item) {
        if (item != null && !selectedList.contains(item)) {
            // 操作原始列表:从可用列表移除,添加到已选列表
            availableList.remove(item);
            selectedList.add(item);
        }
    }

    private void removeItem(String item) {
        if (item != null && !availableList.contains(item)) {
            // 操作原始列表:从已选列表移除,添加到可用列表
            selectedList.remove(item);
            availableList.add(item);
        }
    }
}

关键修改点说明

  1. 移除不必要的列表包装:
    • 删掉了FXCollections.observableList(sortedList),直接用lvAvailable.setItems(sortedList),因为SortedList本身就是ObservableList的实现类,不需要额外包装。
  2. 操作原始数据源而非视图:
    • addItem和removeItem方法中,不再操作lvAvailable.getItems()或lvSelected.getItems(),而是直接修改availableList和selectedList。这样既避免了不可修改列表的异常,又能让过滤/排序视图自动同步更新。
  3. 直接绑定已选列表:
    • 把selectedList直接设置给lvSelected.setItems(selectedList),简化代码逻辑,同时保证已选列表的可编辑性。

这样修改后,你不仅能正常实现双击移动条目的功能,搜索筛选也能正常工作,而且不会再出现UnsupportedOperationException了。

内容的提问来源于stack exchange,提问作者Zephyr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:42:22