[英]Mixing Swing/FX with binding - use custom property for mediating between threads?
這是一種在混合Swing / FX並將兩個部分綁定到同一模型時看到線程規則違規的后續行動。
與此同時,我進行了一些實驗:使用一個自定義屬性,它的唯一任務是分別在EDT / fx-thread上訪問/通知。 這個想法是自定義屬性
擺脫線程規則違規......付出代價:在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<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());
}
部分答案(為什么雙向綁定文本字段的錯誤行為以及針對它的操作):
從技術上講,行為是由內部標志引起的,當雙向綁定屬性的“反向通知”發生在預期的代碼塊之外時,這些標志會混淆。
isUpdating
再次不更新原始屬性 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 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.