[英]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
这个想法是自定义属性
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
在继续之前,问题是
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. 从技术上讲,行为是由内部标志引起的,当双向绑定属性的“反向通知”发生在预期的代码块之外时,这些标志会混淆。
isUpdating
to not update the originating property again isUpdating
再次不更新原始属性 doNotAdjustCaret
is used by TextInputControl to mark changes triggered by itself. 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) 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.