18.4 数据绑定(JFace data binding framework)
在SWT编程中,界面组件对数据的读写是一项很繁重的工作,比如说“第16.2节 向导式对话框”就是较典型的示例。而SWT组件的数据绑定框架的推出将会大大简化这项工作,不过此框架在Eclipse3.2还是测试状态,它所在包是org.eclipse.jface.internal.databinding,包名带internal字样说明它还是仅限于内部使用。相信不久之后此框架将正式发布。
18.4.1 准备工作
先将数据绑定框架的支持包加入到myswt项目的库引用,它的路径是:C:\eclipse\plugins\org.eclipse.jface.databinding_1…0.jar。另外,如果你还想看它的源代码,其源代码包在:C:\eclipse\plugins\org.eclipse.rcp.source_3.2…\src\org.eclipse.jface.databinding_1…\src.zip。
图18.7 JFace data binding的库引用
接着创建两个数据类People和City,如下所示:
public class People {
private String name; // 姓名
private boolean sex; // 性别 true男,flase女
private int age; // 年龄
private List<String> interests;// 兴趣
private List<City> cities;// 此人所工作过的城市
// ------------以下为字段的Setter/Getter方法-------------------
public String getName() { return name; }
public void setName(String string) { name = string; }
public boolean isSex() { return sex; }
public void setSex(boolean sex) { this.sex = sex; }
public int getAge() { return age;}
public void setAge(int i) { age = i; }
public void setInterests(List<String> interests) {
this.interests = interests;
}
public List<String> getInterests() {
if (interests == null) return Collections.emptyList(); //空值是最容易出BUG的地方,
return interests; //尽量不要返回空值
}
public void setCities(List<City> cities) {
this.cities = cities;
}
public List<City> getCities() {
if (cities == null) return Collections.emptyList();
return cities;
}
}
public class City {
private String name;
private String desc;
public City() {}
public City(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getDesc() { return desc; }
public void setDesc(String desc) { this.desc = desc; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() { return name + "," + desc; }
}
然后再创建一个静态工厂类,这个工厂类是通用的,它主要是用来组装一个普遍可用的DataBindingContext对象。DataBindingContext是数据绑定框架的容器,也是我们以后编程中最常用到的一个类,其代码如下所示。
创建DataBindingContext对象不需要参数,同时它也应该及时销毁。DataBindingContext对象为界面组件提供数据绑定服务,如果界面组件没有了,DataBindingContext对象也就没有必要存在了。所以在工厂类的第一个方法createContext(Control control),它传入一个Control对象,我们监听此界面组件的销毁事件,当它被销毁时也顺带把DataBindingContext对象一起销毁。
public class DataBindingContextFactory {
private DataBindingContextFactory() {}
public static DataBindingContext createContext(Control control) {
final DataBindingContext context = createContext();
control.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
context.dispose();
}
});
return context;
}
public static DataBindingContext createContext() {
DataBindingContext context = new DataBindingContext();
context.addObservableFactory(new NestedObservableFactory(context));
context.addObservableFactory(new BeanObservableFactory(context, null, new Class[] { Widget.class }));
context.addObservableFactory(new SWTObservableFactory());
context.addObservableFactory(new ViewersObservableFactory());
context.addBindSupportFactory(new DefaultBindSupportFactory());
context.addBindingFactory(new DefaultBindingFactory());
context.addBindingFactory(new ViewersBindingFactory());
return context;
}
}
18.4.2 数据绑定的简单示例
让我们来看一个最简单的数据绑定实例,其运行效果如图18.8所示。图中按钮用于打印出数据对象的值,这样能方便我们在学习时,随时查看数据对象的变化。图中文本框将用来做数据绑定。
图18.8 示例效果图
示例代码如下所示:
//--------------文件名:JFaceDataBinding1 ----------
final People bean = new People(); //数据
Button button = new Button(shell, SWT.NONE);
button.setText("打印数据");
button.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
System.out.println("---------------------------------");
System.out.println("name=" + bean.getName());
System.out.println("age=" + bean.getAge());
System.out.println("sex=" + bean.isSex());
System.out.println("interests=" + Arrays.toString(bean.getInterests().toArray()));
System. out.println("cities=" + Arrays.toString(bean.getCities().toArray()));
}
});
Text nameText = new Text(shell, SWT.BORDER);
DataBindingContext ctx = DataBindingContextFactory.createContext(shell);
ctx.bind(nameText, new Property(bean, "name"), null);
程序说明:ctx.bind(…)是最关键的是一句,它把数据对象People的属性name封装在一个Property对象中,然后和text绑定在一起。当我们在文本框中输入或修改字符,其变化值将直接更新到People对象的name属性里。
如果你想将sex属性和nameText文本框的其他属性绑定在一起,可以这样:
ctx.bind(new Property(nameText, "enabled"), new Property(bean, "sex"),null);
ctx.bind(new Property(nameText, "visible"), new Property(bean, "sex"),null);
18.4.3 使用BindSpec类定义特殊绑定
更复杂的绑定行为需要运行BindSpec类,该类将涉及到很多JFace数据绑定知识。
1.用BindSpec来作验证
在上面的示例中,ctx.bind(…)的第三个参数设成了空值,该参数为BindSpec类型,可以用它来定义数据的特殊绑定行为。下面的示例就是用BindSpec类来定义一个输入值验证,示例中把age属性绑定在nameText文本框上,并限制文本框只能输入数值,且不能为0。
//--------------文件名:JFaceDataBinding2.java-----------------------
BindSpec spec = new BindSpec(null, null, new MyValidator(), null);
ctx.bind(ageText, new Property(bean, "age"), spec);
其中MyValidator的代码如下。它实现了IValidator接口的两个方法,前一个方法isPartiallyValid比后一个方法isValid被调用的次数要多得多,所以一般用前者进行简单一点的验证,用后者进行较为耗资源的验证。比如说用户登录,isPartiallyValid可以去验证密码是不是数字与及长度是否足够,isValid就可以连接到数据库验证密码是否正确。在本示例,由于对数值验证很简单,不会占太多资源,所以完全可以把isValid里的验证合并到isPartiallyValid中。
private static class MyValidator implements IValidator {
//文本框每次输入字符都将执行此方法
public ValidationError isPartiallyValid(Object value) {
if (NumberUtils.isNumber((String) value))
return null;
else
return new ValidationError(ValidationError.ERROR, "无效的数字");
}
//文本框第一次设值时执行一次此方法,输入完成失去焦点时再执行一次
public ValidationError isValid(Object value) {
if ("0".equals(value))
return ValidationError.error("不允许等于0");
else
return null;
}
}
2.用BindSpec来作值转化
有时候数据模型应用到界面的值需要转化一下,界面的值更新到数据模型前也需要转化一下。如图所示,数据模型中的sex属性是布尔值,而界面显示则是男女字符串,这时sex属性在应用到下拉框之前就需要先转成字符串,而下拉框的字符串选择值更新到sex属性之前也需要转成相应的布尔值。
图18.9 下拉框绑定及值转换化
下面的代码实现了图18.9的效果:
//--------------文件名:JFaceDataBinding3.java-----------------------
// 界面组件
Combo combo = new Combo(shell, SWT.READ_ONLY);
combo.setItems(new String[] { "男", "女" });
// 数据绑定
DataBindingContext ctx = DataBindingContextFactory.createContext(shell);
BindSpec spec = new BindSpec(new BooleanToStringConverter(), new StringToBooleanConverter(), null, null);
ctx.bind(new Property(combo, SWTProperties.SELECTION), new Property(bean, "sex"), spec);
BindSpec构造函数的第一个IConverter参数是数据模型到界面前的转化,第二个IConverter参数是界面更新到数据模型前的转化。SWTProperties.SELECTION意思是指绑定到下拉框的选择。两个IConverter类的实现代码如下:
private static class BooleanToStringConverter implements IConverter {
public Object convert(Object fromObject) {
if ((Boolean) fromObject)
return "男";
else
return "女";
}
public Object getFromType() { return Boolean.TYPE; } //输入值的类型
public Object getToType() { return String.class; } //转化后值的类型
}
private static class StringToBooleanConverter implements IConverter {
public Object convert(Object fromObject) {
return "男".equals(fromObject);
}
public Object getFromType() { return String.class; }
public Object getToType() { return Boolean.TYPE; }
}
3.利用BindSpec的值转化来显示验证错误信息
在本小节的第一个示例对文本框输入做了验证,但是验证的错误信息没有显示出来,本实例将用BindSpec来实现显示错误信息的功能。实现方法是把验证返回的ValidationError类和一个Label绑定在一起,如下代码所示:
//--------------文件名:JFaceDataBinding4.java-----------------------
// 界面组件
Text ageText = new Text(shell, SWT.BORDER);
ageText.setLayoutData(new RowData(50, -1));
Label errorLable = new Label(shell, SWT.BORDER);
errorLable.setLayoutData(new RowData(100, -1));
// 数据绑定
DataBindingContext ctx = DataBindingContextFactory.createContext(shell);
BindSpec spec = new BindSpec(null, null, new MyValidator(), null);
Binding binding = ctx.bind(ageText, new Property(bean, "age"), spec); //取到Binding对象
//将ValidationError类的错误信息和Label做绑定
ValidationErrorToStringConverter converter1 = new ValidationErrorToStringConverter();
ReadOnlyConverter converter2 = new ReadOnlyConverter(String.class, ValidationError.class);
BindSpec spec2 = new BindSpec(converter1, converter2, null, null);
ctx.bind(errorLable, binding.getPartialValidationError(), spec2); // 绑定isPartiallyValid方法的错误
ctx.bind(errorLable, binding.getValidationError(), spec2); // 绑定isValid方法的错误
在以上代码中创建了两个IConverter类,第一个ValidationErrorToStringConverter是把ValidationError转化成一个字符串,其代码如下。这个类可以通用,所以写成一个单独的类。
public class ValidationErrorToStringConverter implements IConverter {
public Object convert(Object fromObject) {
return fromObject == null ? null : fromObject.toString();
}
public Object getFromType() { return ValidationError.class; }
public Object getToType() { return String.class; }
}
第二个ReadOnlyConverter类是定义界面到数据模型的转化,由于错误显示是单向的,不需要界面到数据模型的转化,所以此类的convert方法简单的返回空值。这个类也可以通用,所以写成一个单独的类。
public class ReadOnlyConverter implements IConverter {
private Object from, to;
public ReadOnlyConverter(Object from, Object to) {
this.from = from;
this.to = to;
}
public Object convert(Object fromObject) { return null; }
public Object getFromType() { return from; }
public Object getToType() { return to; }
}
18.4.4 TableViewer的数据绑定
本实例将演示如何对TableViewer做数据绑定,把某People的Cities属性显示在表格中。除了显示记录之外,实例还演示了如何用WritableValue对象来绑定表格的当前选择。本实例完整的代码见JFaceDataBinding5.java。
图18.10 TableViewer的数据绑定
首先要做一下数据准备,在JFaceDataBinding5.java里创建几个City对象,如下所示:
final People bean = new People();// 数据
ArrayList<City> cities = new ArrayList<City>(3);
cities.add(new City("桂林", "山水甲天下"));
cities.add(new City("深圳", "曾经的开放特区"));
cities.add(new City("南宁", "广西的省会城市"));
cities.add(new City("北京", "中国首都"));
bean.setCities(cities);
TablePresentationModel tableModel = new TablePresentationModel(bean.getCities());
TablePresentationModel是针对TableViewer自创的一个数据类,存放了WritableValue对象,和表格显示的City数据集。其中WritableValue对象用来保存表格的当前选择。
public class TablePresentationModel {
private List<City> input;
private WritableValue selected;
public TablePresentationModel(List<City> input) {
this.input = input;
this.selected = new WritableValue(City.class);
this.selected.setValue(input.get(1));// 默认选择第二条记录
}
public List<City> getInput() { return input; }
public void setInput(List<City> input) { this.input = input; }
public WritableValue getSelected() { return selected; }
public void setSelected(WritableValue selected) { this.selected = selected; }
}
接着在JFaceDataBinding5.java里创建表格和一个标签,标签用来显示表格的当前选择。同时把TablePresentationModel 对象tableModel绑定到表格和标签上。
TableViewer tv = new TableViewer(shell, SWT.BORDER | SWT.FULL_SELECTION);
Table table = tv.getTable();
table.setLayoutData(new RowData(150, 70));
table.setHeaderVisible(true); // 显示表头
table.setLinesVisible(true); // 显示表格线
TableLayout layout = new TableLayout();
table.setLayout(layout);
layout.addColumnData(new ColumnWeightData(13));
new TableColumn(table, SWT.NONE).setText("名称");
layout.addColumnData(new ColumnWeightData(40));
new TableColumn(table, SWT.NONE).setText("注释");
Label label = new Label(shell, SWT.BORDER);
// 数据绑定
DataBindingContext ctx = DataBindingContextFactory.createContext(shell);
TableModelDescription dec = new TableModelDescription(new Property(tableModel, "input", City.class, true), new String[] { "name", "desc" });
ctx.bind(new Property(tv, ViewersProperties.CONTENT), dec, null);
ctx.bind(new Property(tv, ViewersProperties.SINGLE_SELECTION), tableModel.getSelected(), null);
ctx.bind(label, new Property(tableModel.getSelected(), "name", String.class, false), null);
程序说明:
● Property(tableModel, "input", City.class, true)封装了tableModel的input属性,第四个参数true指出input属性是一个集合,第三个参数指出此集合的元素是City类型。{ "name", "desc" }指要取出City的name、desc属性显示在表格中。然后所有这些信息都封装在TableModelDescription对象中。
● ViewersProperties.CONTENT表示数据和表格的内容绑定。
● ViewersProperties.SINGLE_SELECTION表示数据和表格的当前所选记录绑定
● 在最后一个bind方法将表格当前选择记录的name属性显示在标签上。
● 用数据绑定框架,不需要设置TableViewer的内容器和标签器了。
18.4.5 Combo绑定和联动
如图18.11所示,本示例将把People数据对象里的interests值填充到一个下拉框,右边的标签将显示下拉框的选择值。
图18.11 Combo绑定和联动
//--------------文件名:JFaceDataBinding6.java-----------------------
final People bean = new People();// 数据
ArrayList<String> interests = new ArrayList<String>(3);
interests.add("阅读");
interests.add("旅行");
interests.add("运动");
bean.setInterests(interests);
………
// 界面组件
Combo combo = new Combo(shell, SWT.BORDER);
Label label = new Label(shell, SWT.BORDER);
label.setLayoutData(new RowData(100, -1));
// 数据绑定
DataBindingContext ctx = DataBindingContextFactory.createContext(shell);
ctx.bind(new Property(combo, SWTProperties.ITEMS), new Property(bean, "interests", String.class, true), null);
WritableValue selectedItem = new WritableValue(String.class);
selectedItem.setValue("旅行"); //设置初选值
ctx.bind(new Property(combo, SWTProperties.SELECTION), selectedItem, null);
ctx.bind(label, selectedItem, null);
程序说明:
● SWTProperties.ITEMS意思是指将interests属性设定为Combo的Items。
● 这里联动的实现还是依靠WritableValue对象,你可以把它看作是JFace绑定框架的一个中转站。先是和下拉框的当前选择记录(SWTProperties.SELECTION)绑定在一起,然后再和label标签绑定在一起。当然也可以直接绑定ctx.bind(label, new Property(combo, SWTProperties.SELECTION), null);不过这样就没法设置下拉框的初选值了。
不过Combo在实际中的应用场景更多是如图18.12那样的,在下拉框预设一些年龄的初始值,然后所选择值将更新到数据对象的age属性中。
图18.12 年龄的绑定
实现代码如下:
//--------------文件名:JFaceDataBinding7.java-----------------------
// 界面组件
Combo combo = new Combo(shell, SWT.BORDER);
// 绑定初始数据
DataBindingContext ctx = DataBindingContextFactory.createContext(shell);
WritableList ageList = new WritableList(String.class);
ageList.add("0");
ageList.add("10");
ageList.add("20");
ageList.add("30");
ctx.bind(new Property(combo, SWTProperties.ITEMS), ageList, null);
ageList.add("100"); // 以后还可以再加进新项,将实现反映到界面上
combo.remove("100");// 如果从界面删除某项,ageList也会跟着删除该项。
// 初选绑定
ctx.bind(new Property(combo, SWTProperties.SELECTION), new Property(bean, "age"), null);
程序说明:WritableList和以前用过的WritableValue都属于IObservable接口,这个接口下具有一个庞大的继承体系,针对不同的组件和应用情况都有相应的实现类。
已有 0 人发表留言,猛击->>这里<<-参与讨论
JavaEye推荐