这是最小化绑定失效的有效方法吗?

时间:2017-08-26 02:33:29

标签: java javafx concurrency javafx-8

我有一些复杂的Observable结构,这可能是也可能不是坏主意,但这不是这个问题的焦点。

这些结构的问题在于它们会产生很多由UI显示的Observable个对象的失效。就像我所知,当JavaFX UI显示某些内容时,它会在其上注册ChangeListener,因此任何使用延迟评估的尝试都会消失。也就是说,使observable无效似乎告诉UI它可能已经改变,这导致UI 立即请求它的值,迫使它立即进行评估。

所以,我想通过Platform.runLater()推迟失效。

我创建了一个名为DeferredBinding的类,它将所有内容委托给包装Binding,但invalidate()方法除外,它将延迟到稍后要处理的JavaFX UI线程。它似乎有效...我可以无效一百次,它似乎只是实际处理失效一次。

但是,我之前没有见过这种模式,我担心它可能属于“不错但尝试不好”的范畴。

所以,问:这是个坏主意吗?我特别关注引入依赖于Observable的其他DeferredBinding对象的错误。一旦Platform.runLater()发生,它们会好吗?

package com.myapp.SAM.model.datastructures;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Binding;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;

/**
 * Specialized binding that defers its invalidations to the JavaFX UI thread in a throttled manner. The idea being that, if invalidate() is called many times,
 * it only basically happens once (when the UI thread gets to it).
 */
public class DeferredBinding<T> implements Binding<T> {

    private static final Logger logger = Logger.getLogger(DeferredBinding.class.getName());

    private final Binding<T> binding;

    private final AtomicBoolean pendingInvalidation = new AtomicBoolean(false);

    public DeferredBinding(Binding<T> binding) {
        this.binding = binding;
    }

    @Override
    public void addListener(ChangeListener<? super T> listener) {
        binding.addListener(listener);
    }

    @Override
    public void removeListener(ChangeListener<? super T> listener) {
        binding.removeListener(listener);
    }

    @Override
    public T getValue() {
        return binding.getValue();
    }

    @Override
    public void addListener(InvalidationListener listener) {
        binding.addListener(listener);
    }

    @Override
    public void removeListener(InvalidationListener listener) {
        binding.removeListener(listener);
    }

    @Override
    public boolean isValid() {
        return binding.isValid();
    }

    /**
     * Override logic for invalidate() method to defer invalidation to runLater. Throttle the invalidations so as not to floor the JavaFX UI thread with
     * multiple calls
     */
    @Override
    public void invalidate() {
        if (pendingInvalidation.getAndSet(true) == false) {
            Platform.runLater(() -> {
                // Signal that the UI is processing the pending invalidation, so any additional invalidations must schedule another update.
                pendingInvalidation.set(false);
                binding.invalidate();
            });
        }
    }

    @Override
    public ObservableList<?> getDependencies() {
        return binding.getDependencies();
    }

    @Override
    public void dispose() {
        binding.dispose();
    }

}

1 个答案:

答案 0 :(得分:2)

我不会提前解决性能问题。测量您的应用程序以确定您是否有问题然后继续..

让我们假设您遇到了问题,有很多方法可以解决您的抽象问题。我想到了三个解决方案:

<强> 1

合并更新(Platform.runLater())以防止FX事件队列饱和,就像您在示例中所做的那样。

由于您只是使绑定无效,因此您不必担心在途中失去价值。因此,似乎(不知道完整的应用程序)这种方式应该有效。

<强> 2

使用Node内置行为标记区域脏(重新)布局。在某些时候,您将调用javafx.scene.Parent.requestLayout()(这是“失效”),这将在将来某个时候调用javafx.scene.Parent.layoutChildren()将脏属性应用于该区域。

第3

以不同方式使用解决方案2:使用虚拟化布局容器。 TableViewTreeTableViewListView都使用虚拟化方法仅更新可见单元格。 有关某些JDK示例,请参阅com.sun.javafx.scene.control.skin.VirtualFlowcom.sun.javafx.scene.control.skin.VirtualContainerBase,另一个示例是Flowless API

由于您没有说明任何细节,我无法再为您提供指导。但应该清楚的是,你的方法可能会很好地工作,还有其他方法。