该场景是一个带有嵌入式 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 月)仍然令人不快地在两者之间切换。