JavaFX使用从非JavaFX线程在UI中使用的提取器更新ObservableList

时间:2018-01-30 10:22:33

标签: java multithreading javafx properties observablelist

我可以访问从非JavaFX线程更新(添加,删除元素)的ObservableList<T>,我在ListView<T>中显示。

如果列表只是在UI中显示它会抛出java.lang.IllegalStateException: Not on FX application thread;,所以我的解决方案是在UI线程中创建第二个列表并监听更改并更新UI线程中的第二个列表:

ObservableList<Person> secondList = FXCollections.observableArrayList();
secondList.addAll(originalList);
originalList.addListener((ListChangeListener<? super Person>)change-> {
    while(change.next()) {
        if (change.wasRemoved()) {
            Platform.runLater(() -> secondList.subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear());
        }

        if (change.wasAdded()) {
            Platform.runLater(() -> secondList.addAll(change.getFrom(), change.getAddedSubList()));
        }
    }
});
ListView<Person> listViewPerson = new ListView<>(secondList);

我认为这应该可以正常工作(如果有任何问题,请指出它们,我忽略了问题简单的排列)。

但是,当列表中有一个提取器时会出现问题:

ObservableList<Person> secondList = FXCollections.observableArrayList(person -> new Observable[]{person.adultProperty()});

对成人属性的更改发生在非javafx线程上,显然会抛出相同的异常或导致一些未定义的UI行为。

我已经考虑过2个解决这个问题的方法:

1)创建原始类的UI等效类并在UI线程中更新其“属性:

public PersonUI(Person person) {
    adult = new SimpleBooleanProperty(person.isAdult());
    person.adultProperty().addListener((observableValue, oldValue, newValue) -> {
        Platform.runLater(() -> this.adult.set(newValue));
    });
}

并在界面显示PersonUI而不是Person

不要认为这是必要的,但这里是此解决方案的完整代码:https://pastebin.com/FYXrre6U

2)使用Platform.runLater()

污染后端代码

还有其他解决方案吗?如果我目前的解决方案有任何问题,请指出。

1 个答案:

答案 0 :(得分:1)

选项3:实现将所有更改传播到fx-thread的transformationList。这是一项前期工作,但可以重复使用。

大纲(未经过正式测试和不完整!)

/**
 * A 1:1 transform of the sourceList that guarantees to fire change notification
 * on the fx-thread.
 * 
 * @author Jeanette Winzenburg, Berlin
 */
public class FXThreadTransformationList<E> extends TransformationList<E, E> {

    public FXThreadTransformationList(ObservableList<E> source) {
        super(source);
    }

    @Override
    protected void sourceChanged(Change<? extends E> c) {
        beginChange();
        while (c.next()) {
            if (c.wasPermutated()) {
                // tbd
            } else if (c.wasUpdated()) {
                update(c);
            } else if (c.wasReplaced()) {
                // tbd
            } else {
                addedOrRemoved(c);
            }
        }
        // commit on fx-thread
        endChangeOnFXThread();
    }

    public void endChangeOnFXThread() {
        Platform.runLater(() -> endChange());
    }

    private void addedOrRemoved(Change<? extends E> c) {
        if (c.wasRemoved()) {
            nextRemove(c.getFrom(), c.getRemoved());
        } else if (c.wasAdded()) {
            nextAdd(c.getFrom(), c.getTo());  
        } else {
            throw new IllegalStateException("expected either removed or added, but was:" + c);
        }
    }

    private void update(Change<? extends E> c) {
        for (int pos = c.getFrom(); pos < c.getTo(); pos++) {
            nextUpdate(pos);
        }
    }

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

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

    @Override
    public E get(int index) {
        return getSource().get(index);
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(FXThreadTransformationList.class.getName());
}