`

数据绑定(JFace data binding framework)

阅读更多

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接口,这个接口下具有一个庞大的继承体系,针对不同的组件和应用情况都有相应的实现类。

3
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics