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

这是一种在混合Swing / FX并将两个部分绑定到同一模型时看到线程规则违规的后续行动。

与此同时,我进行了一些实验:使用一个自定义属性,它的唯一任务是分别在EDT / fx-thread上访问/通知。 这个想法是自定义属性

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

摆脱线程规则违规……付出代价:在fx文本字段中输入时,插入符号设置为文本的开头,从而预先填充每个字符。 在继续之前,问题是

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

代码(可以在上一个问题的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 extends ObjectPropertyBase { // the delegate we are keeping synched to private Property 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 changeListener; public PropertyWrapper(Property 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 createChangeListener() { ChangeListener l = new ChangeListener() { @Override public void changed(ObservableValue 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(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.

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

部分答案(为什么双向绑定文本字段的错误行为以及针对它的操作):

从技术上讲,行为是由内部标志引起的,当双向绑定属性的“反向通知”发生在预期的代码块之外时,这些标志会混淆。

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

现在,线程更改属性落在第一个块之外,触发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 fi 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.

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