在jface databinding中,将普通的java bean(有get/set方法但没有通过PropertyChangeSupport实现属性监控)定义为POJO对象。
我们可以对POJO对象通过PojoProperties.value(String propertyName)
方法提供IObservableValue实例,但返回的PojoValueProperty实例并没有真正实现对POJO对象的监控(参见PojoValueProperty源码)。
所以UI组件与POJO对象之间建立的数据绑定是单向的,UI组件的数据变化可以同步到POJO对象,但反过来不行。
下面这个示例可以演示这个区别,
运行程序,程序启动时,Text组件的内容被更新成POJO对象属性相同的值。
但按”测试”按钮,修改了POJO对象的属性,但Text控件的值并没有同步变化。
package testwb;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Text;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.widgets.Display;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.jface.databinding.swt.DisplayRealm;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.core.databinding.beans.PojoProperties;
public class TestPojoBinding extends Dialog {
/** * 数据对象定义 * @author guyadong * */
public class Configuration {
private String name;
public Configuration(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.printf("updated %s\n",this.name);
}
}
private DataBindingContext m_bindingContext;
/** * 成员变量:数据对象 */
protected Configuration editorConfig=new Configuration("hello!");
private Text myNametext;
/** * Create the dialog. * @param parentShell */
public TestPojoBinding(Shell parentShell) {
super(parentShell);
}
/** * Create contents of the dialog. * @param parent */
@Override
protected Control createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
container.setLayout(null);
Button btnNewButton = new Button(container, SWT.NONE);
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
editorConfig.setName("word");
}
});
btnNewButton.setBounds(38, 154, 80, 27);
btnNewButton.setText("测试");
myNametext = new Text(container, SWT.BORDER);
myNametext.setBounds(38, 27, 80, 23);
return container;
}
/** * Create contents of the button bar. * @param parent */
@Override
protected void createButtonsForButtonBar(Composite parent) {
createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
m_bindingContext = initDataBindings();
}
/** * Return the initial size of the dialog. */
@Override
protected Point getInitialSize() {
return new Point(362, 298);
}
public static void main(String[] args) {
Display display = Display.getDefault();
Realm.runWithDefault(DisplayRealm.getRealm(display), new Runnable() {
public void run() {
try {
TestPojoBinding setting = new TestPojoBinding(null);
setting.open();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
protected DataBindingContext initDataBindings() {
DataBindingContext bindingContext = new DataBindingContext();
IObservableValue observeTextMyNametextObserveWidget = WidgetProperties.text(SWT.Modify).observe(myNametext);
IObservableValue nameEditorConfigObserveValue = PojoProperties.value("name").observe(editorConfig);
bindingContext.bindValue(observeTextMyNametextObserveWidget, nameEditorConfigObserveValue, null, null);
return bindingContext;
}
}
如果想要实现上面例子中数据对象属性与Text组件的内容双向同步绑定。解决方案之一就是改造数据对象Person,通过PropertyChangeSupport实现属性监控。
package testwb;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Text;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.widgets.Display;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.jface.databinding.swt.DisplayRealm;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.core.databinding.beans.BeanProperties;
public class TestPojoBinding2 extends Dialog {
public class ModelObject {
private final PropertyChangeSupport changeSupport =
new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener
listener) {
changeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener
listener) {
changeSupport.removePropertyChangeListener(listener);
}
protected void firePropertyChange(String propertyName, Object oldValue,
Object newValue) {
changeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
}
/** * 数据对象定义,继承ModelObject类,获取属性改变时被监控能力 * @author guyadong * */
public class Person extends ModelObject {
private String name;
public Person(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
// 修改set方法,在修改属性的同时,调用firePropertyChange通知所有侦听器属性已经改变
firePropertyChange("name", this.name, this.name = name);
System.out.printf("updated %s\n",this.name);
}
}
private DataBindingContext m_bindingContext;
/** * 成员变量:数据对象 */
protected Person editorConfig=new Person("hello!");
private Text myNametext;
/** * Create the dialog. * @param parentShell */
public TestPojoBinding2(Shell parentShell) {
super(parentShell);
}
/** * Create contents of the dialog. * @param parent */
@Override
protected Control createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
container.setLayout(null);
Button btnNewButton = new Button(container, SWT.NONE);
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
editorConfig.setName("word");
}
});
btnNewButton.setBounds(38, 154, 80, 27);
btnNewButton.setText("测试");
myNametext = new Text(container, SWT.BORDER);
myNametext.setBounds(38, 27, 80, 23);
return container;
}
/** * Create contents of the button bar. * @param parent */
@Override
protected void createButtonsForButtonBar(Composite parent) {
createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
m_bindingContext = initDataBindings();
}
/** * Return the initial size of the dialog. */
@Override
protected Point getInitialSize() {
return new Point(362, 298);
}
public static void main(String[] args) {
Display display = Display.getDefault();
Realm.runWithDefault(DisplayRealm.getRealm(display), new Runnable() {
public void run() {
try {
TestPojoBinding2 setting = new TestPojoBinding2(null);
setting.open();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
protected DataBindingContext initDataBindings() {
DataBindingContext bindingContext = new DataBindingContext();
// 为Text组件创建观察对象
IObservableValue observeTextMyNametextObserveWidget = WidgetProperties.text(SWT.Modify).observe(myNametext);
// 为数据对象属性创建观察对象
IObservableValue nameEditorConfigObserveValue = BeanProperties.value("name").observe(editorConfig);
// 数据绑定
bindingContext.bindValue(observeTextMyNametextObserveWidget, nameEditorConfigObserveValue, null, null);
//
return bindingContext;
}
}
再运行程序,点击”测试”按钮,Text的值随着数据对象的属性同步改变了。
上面这个方案已经实现了数据对象和UI组件的双向同步更新,但缺点就是需要对POJO对象进行改造,当项目中有多个POJO对象需要实现与UI组件的双同步更新时,这个工作量也是挺大的。
有没有办法在不改变现有POJO对象的代码的情况下,实现双向同步的目标呢?
有,解决方案就是本文的标题jface databinding/PojoBindable。[注意:这还是个实验项目,使用需谨慎]
PojoBindable利用ASM代码动态修改的技术,通过在运行时为POJO对象添加PropertyChangeSupport 的方法并修改setter方法,提供了一个途径让开发者在不修改自己的POJO类代码的情况下让POJO对象拥有完整的数据绑定能力。
凡事都有代价,使用PojoBindable想不修改POJO对象代码就拥有PropertyChangeSupport能力的话,代价是什么呢?
Pojo Bindable是一个Java Agent,所以为了使用PojoBindable,必须在java程序启动时指定jvm参数,用-javaagent
参数指定使用PojoBindable
-javaagent:/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar
需要-Dbindable.packages
指定对哪些pojo对象进行修改java代码
-Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model
必须将 ObjectWeb ASM加入classpath
关于Pojo Bindable配置的更详细说明参见其官网原文:
https://wiki.eclipse.org/JFace_Data_Binding/PojoBindable#With_Pojo_Bindable
参考资料
《JFace Data Binding/PojoBindable》
《AJFace Data Binding - Tutorial》