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

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

问题描述

我的数据源提供了一个 ObservableList< String> ,但是对于我的ListView,我需要一个 ObservableList< Warning>

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

A 警告基本上只是字符串的装饰器,添加一个布尔值来提供跟踪状态的方法的复选框我的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?

更精确的是:如果将字符串添加到或删除从原始列表中,我想立即在Warnings列表中查看此更改,从而查看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(...)时都会创建一个新的警告 在映射列表中。所以

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天全站免登陆