java - 将 Swing/FX 与绑定(bind)混合 - 使用自定义属性在线程之间进行调解?

标签 java multithreading swing binding javafx-2

这是一种看到的后续thread-rule violations when mixing Swing/FX并将两个部分绑定(bind)到同一模型。

与此同时,我做了一些实验:使用自定义属性,其唯一任务是分别负责在 EDT/fx 线程上访问/通知。这个想法是自定义属性

  • 由需要在 EDT 上访问的属性支持
  • 在fx端使用,即从FX-AT调用它的fx api
  • 它的任务是适本地调用/runLater

摆脱违反线程规则的行为……代价是:在 fx 文本字段中键入时,脱字符号设置为文本的开头,因此在每个字符前面。在继续之前,问题是

  • 可能像下面这样的包装器可以工作吗?
  • 它做错了什么吗? (作为游戏的新手,我可能会做一些非常愚蠢的事情;-)
  • 插入符号设置的原因是什么?

代码(可以在上一个问题的 SSCCE 中使用,唯一的改变是取消注释包装器创建并使用它代替直接文本绑定(bind)到字段)

/**
 * Wrapper that switches to FX-AT/EDT as appropriate. The assumption is
 * that the delegate needs to be accessed on the EDT while this property 
 * allows client access on the FX-AT.
 * 
 * @author Jeanette Winzenburg, Berlin
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class PropertyWrapper<T> extends ObjectPropertyBase<T> {
    // the delegate we are keeping synched to
    private Property<T> delegate;
    // the value which is kept in synch (on being notified) with the delegate's value
    // JW: does this make sense at all?
    private volatile T value;
    // keeping a copy of the bean ... ? better not allow accessing at all? 
    // private Object delegateBean;
    private String delegateName;
    private ChangeListener<T> changeListener;

    public PropertyWrapper(Property<T> delegate) {
        this.delegate = delegate;
        bindDelegate();
    }

    /**
     * Returns the value which is kept synched to the delegate's value.
     */
    @Override
    public T get() {
        return value;
    }

    /**
     * Implemented to update the delegate on the EDT
     */
    @Override
    public void set(T value) {
        // PENDING: think about uni-directional binding
        updateToDelegate(value);
    }

    /**
     * Updates the delegate's value to the given value. 
     * Guarantees to do the update on the EDT.
     * 
     * @param value
     */
    protected void updateToDelegate(final T value) {
        if (SwingUtilities.isEventDispatchThread()) {
            doUpdateToDelegate(value);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    doUpdateToDelegate(value);
                }
            });
        }
    }

    /**
     * Updates the delegate's value to the given value
     * This methods runs on the thread that it is called from.
     * 
     * @param the value to set. 
     * 
     */
    private void doUpdateToDelegate(T value) {
        delegate.setValue(value);
    }

    /**
     * Adds a ChangeListener to the delegate and synchs the value
     * to the delegate's value.
     * 
     * This is called once from the constructor, assuming that the thread it is
     * called on is compatible with the delegates threading rules.
     */
    private void bindDelegate() {
        if (changeListener != null) throw new IllegalStateException("cannot bind twice");
        value = delegate.getValue();
        delegateName = delegate.getName();
        changeListener = createChangeListener();
        delegate.addListener( 
                changeListener); 
    }

    /**
     * Creates and returns the ChangeLister that's registered to the delegate.
     * @return
     */
    private ChangeListener<T> createChangeListener() {
        ChangeListener<T> l = new ChangeListener<T>() {

            @Override
            public void changed(ObservableValue<? extends T> observable,
                    T oldValue, T newValue) {
                updateFromDelegate(newValue);

            }

        };
        // weakchangelistener doesn't work ... for some reason
        // we seem to need a strong reference to the wrapped listener
        // return new WeakChangeListener<T>(l);
        return l;
    }

    /**
     * Updates the internal value and notifies its listeners. Schedules the
     * activity for execution on the fx-thread, if not already called on it.
     * 
     * @param newValue
     */
    protected void updateFromDelegate(final T newValue) {
        if (Platform.isFxApplicationThread()) {
            doUpdateFromDelegate(newValue);
        } else {
            Platform.runLater(new Runnable() {

                @Override
                public void run() {
                    doUpdateFromDelegate(newValue);
                }}); 
        }
    }


    /**
     * Updates the internal value and notifies its listeners. It
     * runs on the thread it is called from.
     * 
     * @param newValue the new value.
     */
    protected void doUpdateFromDelegate(T newValue) {
        value = newValue;
        fireValueChangedEvent();
    }

    /**
     * Overridden to guarantee calling super on the fx-thread.
     */
    @Override
    protected void fireValueChangedEvent() {
        if (Platform.isFxApplicationThread()) {
            superFireChangedEvent();
        } else {
            Platform.runLater(new Runnable() {

                @Override
                public void run() {
                    superFireChangedEvent();
                }}); 
        }
    }

    protected void superFireChangedEvent() {
        super.fireValueChangedEvent();
    }

    /**
     * Implemented to return null.<p>
     * PENDING: allow access to delegate's bean? It's risky, as this method
     * most probably will be called on the fx-thread: even if we keep a copy
     * around, clients might poke around the bean without switching to the EDT.
     */
    @Override
    public Object getBean() {
        return null; //delegate != null ? delegate.getBean() : null;
    }

    @Override
    public String getName() {
        return delegateName; //delegate != null ? delegate.getName() : null;
    }

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

最佳答案

部分答案(为什么双向绑定(bind)文本字段的行为不当以及如何应对):

从技术上讲,该行为是由于内部标志引起的,当双向绑定(bind)属性的“返回通知”发生在预期代码块之外时,内部标志会造成混淆。

  • BidirectionalBinding 守卫 - 作为两个属性的监听器 - 本身防止 isUpdating 的无限循环,以不再更新原始属性
  • 标志doNotAdjustCaret 由TextInputControl 用来标记由其自身触发的更改。该控件有一个自定义 TextProperty,它使用该标志将选择设置为开始(对于外部更改)或不设置(对于内部更改)

现在 thread-changing 属性落在第一个 block 之外,触发 textProperty 的重新设置,这反过来又不被识别为自触发,从而重置 selectin/caret。解决方法是更新值并直接“适得其反”:

/**
 * Implemented to set the value of this property, immediately 
 * fire a value change if needed and then update the delegate.
 * 
 * The sequence may be crucial if the value is changed by a bidirectionally
 * bound property (like f.i. a TextProperty): that property reacts to 
 * change notifications triggered by its own change in a different 
 * way as by those from the outside, detected by a flag (sigh ...)
 * set while firing.
 */
@Override
public void set(T value) {
    T oldValue = this.value;
    this.value = value;
    if (!areEqual(oldValue, value)) {
        fireValueChangedEvent();
    }
    updateToDelegate(value);
    // PENDING: think about uni-directional binding
}

/**
 * Updates the internal value and notifies its listeners, if needed.
 * Does nothing if the newValue equals the current value.<p>
 * 
 * It runs on the thread it is called from.
 * 
 * @param newValue the new value.
 */
protected void doUpdateFromDelegate(T newValue) {
    if (areEqual(newValue, value)) return;
    value = newValue;
    fireValueChangedEvent();
}

关于java - 将 Swing/FX 与绑定(bind)混合 - 使用自定义属性在线程之间进行调解?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19656810/

相关文章:

java - 使用 Mockito 进行单元测试需要哪些导入?

java - 如果站点正在使用 Ajax,如何使用 Selenium WebDriver 检查?

c++ - C++中的线程共享数据

python - 两个线程使用相同的函数

java - GraphView 中轴的最大值

java - 如何从 ArrayList<Object> 中分离数据

Java 非法监视器状态异常

java - 显示从 Mysql (blob) 到 Jtable 的图像 - DefaultTableModel

java - 如何创建 JButton 数组?

java - SwingWorker.process 没有被调用