简体   繁体   English

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

[英]Mixing Swing/FX with binding - use custom property for mediating between threads?

This is a kind of follow-up of seeing thread-rule violations when mixing Swing/FX and bind both parts to the same model. 这是一种在混合Swing / FX并将两个部分绑定到同一模型看到线程规则违规的后续行动。

Meanwhile I experimented a bit: use a custom property whose only task it is take care of accessing/notifying on the EDT/fx-thread, respectively. 与此同时,我进行了一些实验:使用一个自定义属性,它的唯一任务是分别在EDT / fx-thread上访问/通知。 The idea is that the custom property 这个想法是自定义属性

  • is backed by a property which needs to be accessed on the EDT 需要在EDT上访问的属性支持
  • is used on the fx side, that is its fx api is called from the FX-AT 在fx端使用,即从FX-AT调用它的fx api
  • its task is to invoke/runLater as appropriate 它的任务是适当地调用/ runLater

Gets rid of the thread rule violations ... at a price: when typing in the fx textfield, the caret is set to the start of the text, thus prepending each character. 摆脱线程规则违规......付出代价:在fx文本字段中输入时,插入符号设置为文本的开头,从而预先填充每个字符。 Before going on, questions are 在继续之前,问题是

  • is it possible that a wrapper like the one below can work? 是否有可能像下面这样的包装工作?
  • is it doing something wrong? 它做错了吗? (Being a bloody newbie to the game, I might be doing something incredibly stupid ;-) (作为游戏的血腥新手,我可能会做一些非常愚蠢的事情;-)
  • what's the reason for the caret setting? 插入符号设置的原因是什么?

The code (can be played with in the SSCCE of the previous question, single change is to uncomment the wrapper creation and use that in place of the direct text binding to the field) 代码(可以在上一个问题的SSCCE中播放,单个更改是取消注释包装器创建并使用它代替直接文本绑定到字段)

/**
 * 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());
}

A partial answer (why the misbehaviour of the bidi-bound textfield and what to do against it): 部分答案(为什么双向绑定文本字段的错误行为以及针对它的操作):

Techically, the behaviour is due to internal flags which get confused when the "back-notification" of a bidi-bound property happens outside of the expected code blocks. 从技术上讲,行为是由内部标志引起的,当双向绑定属性的“反向通知”发生在预期的代码块之外时,这些标志会混淆。

  • BidirectionalBinding guards - being the listener to two properties - itself against endless looping by isUpdating to not update the originating property again BidirectionalBinding守卫 - 作为两个属性的监听器 - 本身反对无限循环isUpdating再次不更新原始属性
  • the flag doNotAdjustCaret is used by TextInputControl to mark changes triggered by itself. TextInputControl使用标志doNotAdjustCaret来标记由其自身触发的更改。 The control has a custom TextProperty which uses that flag to either set the selection to the start (for external changes) or not (for internal changes) 该控件有一个自定义TextProperty,它使用该标志将选择设置为开始(对于外部更改)或不(对于内部更改)

Now the thread-changing property falls outside the first block, triggers a re-set of the textProperty which in turn isn't recognized as self-triggered, thus resetting the selectin/caret. 现在,线程更改属性落在第一个块之外,触发textProperty的重置,而后者又不被识别为自触发,因此重置了selectin / caret。 A work-around is to update the value and "back-fire" directly: 解决方法是直接更新值和“回火”:

/**
 * 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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM