3

该场景是一个带有嵌入式 FX 组件的 Swing ui - Swing 和 FX 部分都应该由相同的模型驱动。假设模型是一些具有绑定属性的 bean,我们可以对两者都使用 fx 绑定,使用 fx 支持来调整 bean 属性和 fx 属性,例如:

// 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;
}

用于将模型属性绑定到 swing 和 fx 组件

// 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);

这很酷......除了我们在两个部分都违反了单线程规则(最后的完整示例)。

那么问题来了:如何解决这些违规行为?希望我只是忽略了一些明显的东西:-)


这是一个可以玩的 SSCCE

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

一颗假豆

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

}

编辑

尝试了一个想法- 它没有完全起作用,所以这是另一个问题;-)

编辑 2

隧道尽头可能会有一些曙光(可能是 mKorbel 在他的评论中的意思):可能支持jdk8 中 EDT/FX-AT 的无缝互操作性。另一方面,最近关于新 SwingNode 的教程(2013 年 9 月)仍然令人不快地在两者之间切换。

4

0 回答 0