装饰 ObservableList 并保留更改事件的最佳实践 [英] Best practice to decorate an ObservableList and retain change events

查看:26
本文介绍了装饰 ObservableList 并保留更改事件的最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的数据源提供了一个 ObservableList,但是对于我的 ListView,我需要一个 ObservableList.

My datasource provides an ObservableList<String>, however for my ListView I need an ObservableList<Warning>.

A Warning 基本上只是字符串的装饰器,添加一个布尔值以提供跟踪复选框状态的方法我的 ListView,如 这个答案.

A Warning is basically just a decorator for the strings, adding a boolean to provide a means for tracking the state of checkboxes my ListView, as proposed in this answer.

class Warning {

    private final ReadOnlyStringWrapper name;
    private final BooleanProperty checked;

    /* ... */

}

目前我正在观察原始列表中的更改事件并手动添加/删除警告列表中的项目:

Currently I am observing change-events in the original list and add/remove items in the warnings list manually:

ObservableList<String> stringList = ...;
ObservableList<Warning> warningList = ...;

stringList.addListener(new ListChangeListener<String>() {

    @Override
    public void onChanged(ListChangeListener.Change<? extends String> change) {
        if (change.wasAdded()) {
            warningList.addAll(change.getAddedSubList().stream().map(Warning::new).collect(Collectors.toList()));
        } else if (change.wasRemoved()) {
            change.getRemoved().forEach(str -> {
                warningList.removeIf(w -> str.equals(w.name));
            });
        }
    }

});

我的问题是:有没有更优雅的方法来装饰我的字符串类型列表,这样它就可以用作警告类型列表而无需手动传递更改事件?

My question is: Is there a more elegant way to decorate my String-typed list, so it can be used as a Warning-typed list without manually passing through change events?

更准确地说:如果在原始列表中添加或删除字符串,我希望立即在警告列表和 ListView 中看到此更改.

To be more precise: If a string is added to or removed from the original list, I want to see this change immediately in the Warnings-list and thus the ListView.

推荐答案

自从你发布它以来,我一直在考虑这个问题.使用我在评论中建议的 EasyBind 是行不通的,因为每次在映射列表上调用 get(...) 时它都会创建一个新的 Warning.所以

I've been thinking about this a bit since you posted it. Using EasyBind as I had suggested in the comment won't work, since it would create a new Warning every time you called get(...) on the mapped list. So

stringList.add("foo");
warningList.get(0).setChecked(true);
assert warningList.get(0).isChecked();

会失败.

此外,如果源列表 (stringList) 中有重复的条目,您的机制就会出错(我认为),因为您将从 warningList 中删除所有相应的条目当从 stringList 中删除单个元素时.事实上,让被删除的元素正确是相当棘手的.

Additionally, your mechanism goes wrong (I think) if you have duplicate entries in the source list (stringList), as you would remove all corresponding entries from the warningList when a single element was removed from the stringList. In fact, getting the removed elements correct is quite tricky.

这里是基于 Tomas Mikula 的 MappedList 缓存源元素和映射元素之间的映射.它使用 IdentityHashMap 来确保重复元素在两个列表中的行为正确.请注意,这仅适用于您希望在将项目添加到源列表时创建新对象的特定情况,因此它不打算(也不适用)替代 EasyBind 中的机制.

Here is a solution based on Tomas Mikula's MappedList which caches the mapping between the source elements and the mapped elements. It uses an IdentityHashMap to ensure that duplicate elements behave correctly in both lists. Note this only works for the specific case where you want to create new objects when items are added to the source list, so it is not intended (and would not work) as a replacement for the mechanism in EasyBind.

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.transformation.TransformationList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class WrappedObjectListExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        ObservableList<String> stringList = FXCollections.observableArrayList("One", "Two", "Three");
        ObservableList<Warning> warningList = new CachingMappedList<Warning, String>(stringList, Warning::new);

        ListView<String> stringListView = new ListView<>(stringList);
        ListView<Warning> warningListView = new ListView<>(warningList);

        warningListView.setCellFactory(CheckBoxListCell.forListView(Warning::checkedProperty));

        TextField textField = new TextField();
        textField.setOnAction(e -> {
            if (! textField.getText().isEmpty()) {
                stringList.add(textField.getText());
                textField.setText("");
            }
        });

        Button remove = new Button("Remove");
        remove.setOnAction(e -> stringList.remove(stringListView.getSelectionModel().getSelectedIndex()));
        remove.disableProperty().bind(stringListView.getSelectionModel().selectedItemProperty().isNull());

        HBox lists = new HBox(10, stringListView, warningListView);
        VBox root = new VBox(10, lists, textField, remove);
        root.setPadding(new Insets(20));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static class Warning {
        private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
        private final BooleanProperty checked = new SimpleBooleanProperty();

        public Warning(String name) {
            this.name.set(name);
        }

        public final ReadOnlyStringProperty nameProperty() {
            return this.name.getReadOnlyProperty();
        }

        public final String getName() {
            return this.nameProperty().get();
        }

        public final BooleanProperty checkedProperty() {
            return this.checked;
        }

        public final boolean isChecked() {
            return this.checkedProperty().get();
        }

        public final void setChecked(final boolean checked) {
            this.checkedProperty().set(checked);
        }

        @Override
        public String toString() {
            return getName();
        }

    }

    public static class CachingMappedList<S,T> extends TransformationList<S, T> {

        private final Function<T, S> mapper ;

        private final IdentityHashMap<T, S> cache ;

        public CachingMappedList(ObservableList<T> source, Function<T,S> mapper) {
            super(source);
            this.mapper = mapper ;
            this.cache = new IdentityHashMap<>();
        }

        @Override
        protected void sourceChanged(Change<? extends T> c) {

            fireChange(new Change<S>(this) {

                @Override
                public boolean wasAdded() {
                    return c.wasAdded();
                }

                @Override
                public boolean wasRemoved() {
                    return c.wasRemoved();
                }

                @Override
                public boolean wasReplaced() {
                    return c.wasReplaced();
                }

                @Override
                public boolean wasUpdated() {
                    return c.wasUpdated();
                }

                @Override
                public boolean wasPermutated() {
                    return c.wasPermutated();
                }


                @Override
                public boolean next() {
                    return c.next();
                }

                @Override
                public void reset() {
                    c.reset();
                }

                @Override
                public int getFrom() {
                    return c.getFrom();
                }

                @Override
                public int getTo() {
                    return c.getTo();
                }

                @Override
                public List<S> getRemoved() {
                    List<S> removed = new ArrayList<>();
                    c.getRemoved().forEach(t -> removed.add(cache.get(t)));
                    return removed;
                }

                @Override
                public int getPermutation(int i) {
                    return c.getPermutation(i);
                }

                @Override
                protected int[] getPermutation() {
                    throw new AssertionError("Unreachable code");
                }

            });

            // clean up cache:

            c.reset();
            while (c.next()) {
                if (c.wasRemoved()) {
                    c.getRemoved().forEach(cache::remove);
                }
            }            
        }

        @Override
        public int getSourceIndex(int index) {
            return index ;
        }

        @Override
        public S get(int index) {
            return cache.computeIfAbsent(getSource().get(index), mapper);
        }

        @Override
        public int size() {
            return getSource().size();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

这篇关于装饰 ObservableList 并保留更改事件的最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆