title: Mixing Swing/FX with binding - how to comply to respective threading rules?
body: <p>The scenario is a Swing ui with embedded FX components - both the Swing and the FX part should be driven by the same model. Assuming the model is some bean with bound properties, we can use fx bindings for both, using the fx support for adapting beans properties and fx properties, something like:</p>
  
  <pre><code>// adapts a bean property to a fx property
  protected Property createBeanAdapter(Object bean, String propertyName) {
      try {
          return JavaBeanObjectPropertyBuilder.create()
                  .bean(bean)
                  .name(propertyName)
                  .build();
      } catch (NoSuchMethodException e) {
          e.printStackTrace();
      }
      return null;
  }
  </code></pre>
  
  <p>Usage for binding a model property to both swing and fx component</p>
  
  <pre><code>// the model
  person = new PersonBean("Philopator");
  personProperty =  createBeanAdapter(person, "lastName");
  // bind to swing label's text
  labelProperty = createBeanAdapter(label, "text");
  labelProperty.bindBidirectional(personProperty);
  // bind to fx textfield
  fxField.textProperty().bindBidirectional(personProperty);
  </code></pre>
  
  <p>That's cool ... except that we are violating the single thread rule in both parts (full example at the end). </p>
  
  <p>So the question is: how to resolve these violations? Hopefully, I'm simply overlooking something obvious :-)</p>
  
  <hr>
  
  <p>Here's an SSCCE to play with </p>
  
  <pre><code>import java.awt.GridLayout;
  import java.awt.event.ActionEvent;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.util.logging.Logger;
  
  import javafx.application.Platform;
  import javafx.beans.property.Property;
  import javafx.beans.property.adapter.JavaBeanObjectPropertyBuilder;
  import javafx.beans.value.ChangeListener;
  import javafx.beans.value.ObservableValue;
  import javafx.embed.swing.JFXPanel;
  import javafx.scene.SceneBuilder;
  import javafx.scene.control.TextField;
  import javafx.scene.layout.VBoxBuilder;
  
  import javax.swing.AbstractAction;
  import javax.swing.Action;
  import javax.swing.BorderFactory;
  import javax.swing.Box;
  import javax.swing.JButton;
  import javax.swing.JComponent;
  import javax.swing.JFrame;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.SwingUtilities;
  
  import fx.property.PersonBean;
  
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public class MixWithBinding {
      // model 
      private PersonBean person;
      // bindings
      private Property personProperty;
      private Property labelProperty;
      // swing components
      private JComponent content;
      private JButton resetButton;
      private JLabel label;
      // fx components   
      private TextField fxField;
  
      public MixWithBinding() {
          // init the view
          JComponent swingPanel = createSwingPanel();
          JComponent fxPanel = createFXPanel();
          content = new JPanel(new GridLayout(0, 2));
          content.add(fxPanel);
          content.add(swingPanel);
          // init/bind the model
          person = new PersonBean("Philopator");
          // adapt to fx property
          personProperty =  createBeanAdapter(person, "lastName");
          // swing binding
          labelProperty = createBeanAdapter(label, "text");
          labelProperty.bindBidirectional(personProperty);
          Action reset = new AbstractAction("Reset Text") {
  
              @Override
              public void actionPerformed(ActionEvent e) {
                  person.setLastName("Philator");
              }
          };
          resetButton.setAction(reset);
          // fx binding
          //final Property threadWrapper = new PropertyWrapper(personProperty);
          Platform.runLater(new Runnable() {
              @Override
              public void run() {
                  //fxField.textProperty().bindBidirectional(threadWrapper);
                  fxField.textProperty().bindBidirectional(personProperty);
              }
          });
  
          debugThreadViolations();
      }
  
      protected Property createBeanAdapter(Object bean, String propertyName) {
          try {
              return JavaBeanObjectPropertyBuilder.create()
                      .bean(bean)
                      .name(propertyName)
                      .build();
          } catch (NoSuchMethodException e) {
              e.printStackTrace();
          }
          return null;
      }
  
      private JComponent createSwingPanel() {
          label = new JLabel();
          resetButton = new JButton();
          JComponent panel = Box.createVerticalBox();
          panel.setBorder(BorderFactory.createTitledBorder("Swing"));
          panel.add(label);
          panel.add(resetButton);
          return panel;
      }
  
      private JComponent createFXPanel() {
          final JFXPanel fxPanel = new JFXPanel();
          Platform.runLater(new Runnable() {
              @Override
              public void run() {
                  fxField = new TextField();
                  fxPanel.setScene(SceneBuilder.create()
                      .root(VBoxBuilder.create()
                          .children(fxField)
                          .build())
                      .build());
  
              }
          });
          JComponent panel = new JPanel();
          panel.setBorder(BorderFactory.createTitledBorder("FX"));
          panel.add(fxPanel);
          return panel;
      }
  
      protected void debugThreadViolations() {
          PropertyChangeListener swingChange = new PropertyChangeListener() {
  
              @Override
              public void propertyChange(PropertyChangeEvent evt) {
                  if (!SwingUtilities.isEventDispatchThread())
                      LOG.info("Violation of EDT rule");
              }
          };
          label.addPropertyChangeListener("text", swingChange);
  
          final ChangeListener fxChange = new ChangeListener() {
  
              @Override
              public void changed(ObservableValue arg0, Object arg1, Object arg2) {
                  if (!Platform.isFxApplicationThread())
                      LOG.info("Violation of FX-AT rule");
              }
  
          };
          Platform.runLater(new Runnable() {
              @Override
              public void run() {
                  fxField.textProperty().addListener(fxChange);
              }
          });
      }
  
      public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
              @Override
              public void run() {
                  JFrame frame = new JFrame("");
                  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                  frame.add(new MixWithBinding().content);
                  frame.setLocationByPlatform(true);
                  frame.setSize(400, 200);
                  frame.setVisible(true);
              }
          });
      }
  
      @SuppressWarnings("unused")
      private static final Logger LOG = Logger.getLogger(MixWithBinding.class
              .getName());
  }
  </code></pre>
  
  <p>A dummy bean</p>
  
  <pre><code>public class PersonBean {
  
      String lastName;
  
      /**
       * @param lastName
       */
      public PersonBean(String lastName) {
          super();
          this.lastName = lastName;
      }
  
      /**
       * @return the lastName
       */
      public String getLastName() {
          return lastName;
      }
  
      /**
       * @param lastName the lastName to set
       */
      public void setLastName(String lastName) {
          Object oldValue = getLastName();
          this.lastName = lastName;
          firePropertyChange("lastName", oldValue, getLastName());
      }
  
      PropertyChangeSupport support = new PropertyChangeSupport(this);
  
      public void addPropertyChangeListener(PropertyChangeListener l) {
          support.addPropertyChangeListener(l);
      }
  
      public void removePropertyChangeListener(PropertyChangeListener l) {
          support.removePropertyChangeListener(l);
      }
  
      protected void firePropertyChange(String name, Object oldValue,
              Object newValue) {
          support.firePropertyChange(name, oldValue, newValue);
      }
  
  }
  </code></pre>
  
  <p><strong>Edit</strong></p>
  
  <p><a href="https://stackoverflow.com/q/19656810/203657">experimented with an idea</a> - which isn't fully working, so it's another question ;-)</p>
  
  <p><strong>Edit 2</strong></p>
  
  <p>there could be some light at the end of the tunnel (and probably what mKorbel meant in his comments): there might be support for <a href="http://wiki.apidesign.org/wiki/JavaFX" rel="nofollow noreferrer">seemless interoperability of EDT/FX-AT in jdk8</a>. On the other hand, a recent <a href="http://docs.oracle.com/javafx/8/embed_swing/jfxpub-embed_swing.htm" rel="nofollow noreferrer">tutorial on the new SwingNode</a> (Sept. 2013) is still unhappily switching between both. </p>