树表视图选择包含源更改后的空值



基本值列表由(变化的)谓词过滤。FilteredList映射到 TreeItem s,然后将此结果列表用作TreeItem子项的根。

TreeTableView上进行选择并在之后谓词更改时,访问所选项目会导致NullPointerException

在我看来,更改中包含的项目是null.这个粗略的概念是否存在设计缺陷?

对于

TreeViewListView,不会发生这种情况

我尝试使用 https://github.com/TomasMikula/EasyBind 进行映射来生成MCVE:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.fxmisc.easybind.EasyBind;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Spinner;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
    // fields protect bound lists from GC
    private ObservableList<DataItem> itemizedDataPool;
    private FilteredList<Data> filteredDataPool;
    private ObservableList<Data> selectedData;
    static class Data {
        final int value;
        public Data(int value) {
            this.value = value;
        }
    }
    static class DataItem extends TreeItem<Data> {
        final Data data;
        public DataItem(Data data) {
            this.data = data;
        }
    }
    @Override
    public void start(Stage primaryStage) throws IOException {
        List<Data> dataPool = new ArrayList<Data>();
        for (int i = 1; i < 20; i++) {
            dataPool.add(new Data(i));
        }
        filteredDataPool = new FilteredList<>(FXCollections.observableArrayList(dataPool));
        TreeTableView<Data> listView = createTreeTableView();
        Spinner<?> lowerBoundSelector = createLowerBoundFilter();
        Label sumLabel = createSummarizingLabel(listView.getSelectionModel().getSelectedItems());
        Parent root = new VBox(listView, lowerBoundSelector, sumLabel);
        Scene scene = new Scene(root, 768, 480);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    private TreeTableView<Data> createTreeTableView() {
        itemizedDataPool = EasyBind.map(filteredDataPool, DataItem::new);
        TreeItem<Data> itemRoot = new TreeItem<>();
        Bindings.bindContent(itemRoot.getChildren(), itemizedDataPool);
        TreeTableView<Data> listView = new TreeTableView<>(itemRoot);
        listView.setShowRoot(false);
        itemRoot.setExpanded(true);
        listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        listView.getColumns().add(new TreeTableColumn<>("Data"));
        return listView;
    }
    private Label createSummarizingLabel(ObservableList<TreeItem<Data>> selectedItems) {
        Label sumLabel = new Label();
        selectedData = EasyBind.map(selectedItems, (TreeItem<Data> t) -> ((DataItem) t).data);
        selectedData.addListener(new InvalidationListener() {
            @Override
            public void invalidated(Observable observable) {
                int sum = 0;
                for (Data d : selectedData) {
                    sum += d.value;
                }
                sumLabel.setText("Sum: " + sum);
            }
        });
        return sumLabel;
    }
    private Spinner<Integer> createLowerBoundFilter() {
        Spinner<Integer> lowerBoundSelector = new Spinner<>(0, 20, 0, 1);
        lowerBoundSelector.valueProperty().addListener(new InvalidationListener() {
            @Override
            public void invalidated(Observable observable) {
                filteredDataPool.setPredicate(t -> t.value > lowerBoundSelector.getValue());
            }
        });
        return lowerBoundSelector;
    }
    public static void main(String[] args) {
        launch(args);
    }
}

问题

TreeTableView使用TreeTableViewArrayListSelectionModel,它扩展MultipleSelectionModelBase,它使用ReadOnlyUnbackedObservableList,它使用(并包含)SelectionListIterator,它的方法nextIndex有一个断开的实现。

感谢法比安指出这一点。他还提交了一份错误报告(http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8145887)。

解决方法

在两者之间使用缓冲区可以为上述问题提供有效的解决方法。我尝试了几种方法。 关于选择无效和Bindings.bindContent setAll不起作用。在这两种情况下,我都收到了列表中的null值。直接的"解决方案"是简单地过滤掉null。这导致了下面低效但显然有效的代码。

// [...]
TreeTableView<Data> listView = createTreeTableView();
selectionBuffer = FXCollections.observableArrayList();
listView.getSelectionModel().getSelectedItems().addListener(new InvalidationListener() {
    @Override
    public void invalidated(Observable observable) {
        selectionBuffer.clear();
        for (TreeItem<Data> t : listView.getSelectionModel().getSelectedItems()) {
            if (t != null) {
                selectionBuffer.add(t);
            }
        }
    }
});
// [...]

现在应该使用 selectionBuffer 而不是 listView.getSelectionModel().getSelectedItems() 来补偿 nextIndex 中的实现问题。

最新更新