Blend Swing / FX with binding - use a custom property to mediate between threads?

This is a kind of continuation of monitoring violations of flow rules when mixing Swing / FX and linking both parts to the same model.

Meanwhile, I experimented a bit: use a custom property whose only task is to take care of access / notification in EDT / fx-thread, respectively. The idea is that a custom property

  • supported by property to be accessed by EDT
  • used on the fx side, i.e. its fx api is called from FX-AT
  • its task is to call / runLater accordingly

Gets an exemption from flow rule violations ... at a price: when you enter the fx text field, the carriage is placed at the beginning of the text, thereby adding each character. Before continuing, questions

  • Is it possible that a wrapper like the one below might work?
  • is something wrong? (Being a bloody newbie in the game, I could be doing something incredibly stupid ;-)
  • What is the reason for setting the carriage?

Code (can be reproduced in SSCCE of the previous question, one change is to uncomment the creation of the shell and use this instead of directly linking text to the field)

/**
 * 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 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 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 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 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 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 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 bean? It 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());
}
+4
source share
1 answer

( , ):

, , , " " bidi-bound .

  • - - isUpdating, .
  • doNotAdjustCaret TextInputControl, , . TextProperty, ( ) ( )

, textProperty, , , self-triggered, 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();
}
+3

All Articles