科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Eclipse设计实现可重用的SWT构件

Eclipse设计实现可重用的SWT构件

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文从创建一个简单的SWT Tree Table开始,引入可重用的用户界面构件这个开发人员普遍关心的问题,然后分析Eclipse的用户界面的一些设计模式,循序渐进的向读者展示了如何设计实现一个精巧的高度可重用的TreeTable构件。

作者:翁长河 来源:论坛整理 2007年11月21日

关键字:

  • 评论
  • 分享微博
  • 分享邮件

在本页阅读全文(共6页)

4.使用TreeTable构件的示例

  基于以上实现的TreeTable构件,我们如果要实现第一部分的示例界面,需要做那些工作呢?首先我们要实现类派生于AbstractField的类充当表的4个列控制器,每个Field的getColumnHeaderText就控制该列的显示,我们依次实现了NameField、TitleField、PhoneField和BirthField。读者可以参考NameField的代码,详细代码请见附件。

  清单10:NameField的代码

public class NameField  extends AbstractField {
public String getColumnHeaderText() {
return "Name";
}
public String getValue(Object object ) {
if(object instanceof Employee)
return ((Employee)object).getName();
else if(object instanceof Department)
return ((Department)object).getName();
else
return "";
}
}

  同时我们需要实现一个适配器EmployeeTreeTableAdapter来控制TreeTable行的行为,这里因为行之间的行为比较简单,我们只要使用一个适配器就能应付需求,如果有更复杂的需求,我们可以轻易的使用新的适配器,来扩展TreeTable行控制能力。清单11给出EmployeeTreeTableAdapter的代码。

  清单11: EmployeeTreeTableAdapter的代码

public class EmployeeTreeTableAdapter   extends DefaultTreeTableAdapter {
public static EmployeeTreeTableAdapter instance=new EmployeeTreeTableAdapter();
private EmployeeTreeTableAdapter(){}
public Object[] getChildren(Object parentElement) {
if(parentElement instanceof Department&&((Department)parentElement).getManagers()!=null)
return ((Department)parentElement).getManagers().toArray();
else if(parentElement instanceof Manager&&((Manager)parentElement).getEmployees()!=null)
return ((Manager)parentElement).getEmployees().toArray();
else
return new Object[0];
}
}

  注意,这里我们返回new Object[0] 而不是null,可以避免引起不必要的判空。 此时,测试代码也变得简单多了(说明实际的开发代码将更为简单),如清单12所示。

  清单12:TreeTable的测试代码

public class TestWindow extends ApplicationWindow {
   public TestWindow() {
    super(null);
  }
   public void run() {
    setBlockOnOpen(true);
    open();
    Display.getCurrent().dispose();
  }
  protected void configureShell(Shell shell) {
    super.configureShell(shell);
    shell.setText("Reusable Tree Table Test");
  }

  protected Control createContents(Composite parent) {
  IField[] fields = new IField[] { new   NameField(), new TitleField(),
  new PhoneField(), new BirthField() };

  TreeTable treeTable= new TreeTable(parent, SWT.BORDER ,fields,
  new FixedTreeTableAdapterFactory(EmployeeTreeTableAdapter.instance) );

  treeTable.setInput(TestDataGenerator.getTestDepartments());
    return treeTable.getTree();
  }
   public static void main(String[] args) {
    new TestWindow().run();
  }
}

  TestWindow的运行结果和图2完全相同。这里的TreeTable构件,使用非常简单,你可以像使用一个普通的SWT构件一样使用它,并且TreeTable的实现使用了一系列细粒度的控制器,读者可以参考图8的类图。

  图8:TestWindow中TreeTable的构成

  TestWindow中TreeTable的构成

  从图8中我们可以看出,虽然我们新建了5个类,多于在第一部分创建的2个类,但是很好的隔离了行和每个列的控制,结构具有松散耦合的特点,具有很好的扩展性,我们将在第四部分演示如何扩展。本部分的示例代码参见附件中的test_1.0.0.jar。 这种细粒度的控制可以完全的分散程序的功能,也就大大简化了代码的结构,清晰易读,具有自解释的功能,大量的细粒度的类和方法,本身就是最好的注释,这样的代码具有很强的可维护性,在大规模的项目中,还有助于快速定位和解决问题。

  也许读者已经注意到EmployeeTreeTableAdapter中的判断代码,如果情况再复杂一点,我们就需要为三种数据类型分别构建3个Adapter,这里我们只使用一个,把它简单化处理了。这样的判断不能避免,但是我们可以通过将判断转移到适配器工厂,可以只要判断一次,将不同的逻辑分散于不同的适配器中,避免重复的判断代码,重复的代码维护起来很不方便。

  有经验读者可能会问,既然如此强调可重用性,为什么不为TreeTable建立一些扩展点,用添加扩展的方法去控制该实例的行为呢?就本文的示例而言,我们的考虑是这样的:

  1 Eclipse的PDE中扩展编辑器功能较弱,基本无法进行有效的语法检查和正确性校验(初学者也许有过在plugin.xml里面写错一个字母而浪费半天时间调试的经历)。

  2 对于这样一个具体的构件,用扩展点来做就未免有点问题扩大化,将会牺牲了程序的简洁和优美,降低开发速度。

  3 扩展点虽然可以实现松耦合,但是隔断了代码之间的关联关系,为程序的编写带来不便。

  实际上,Eclipse程序员会有一种倾向,有人会习惯而自然的为所有可重用的组件添加扩展点,期望用扩展的方法来重用代码。从而造成扩展点过多,程序结构过于复杂。而我们认为最适合的就是最好的,扩展点的约束和控制条件都相对较弱,过量的使用扩展点和扩展会破坏程序的连贯性,对开发过程反而有害。因此我们不建议在Eclipse项目中滥用扩展点,Eclipse的扩展点机制可以增强程序的整体可延伸性,但是细粒度的局部的构件,采用面向对象的传统方法实现重用,无论从代码的可读性和开发的效率来看,都是非常适合的。

  5.增加TreeTable构件的功能

  现在我们将向您展示该构件超强的扩展能力,我们将轻松的实现TreeTable的列排序的功能,然后为特定的行增加背景色。上文的TreeTable实现中,相关的接口都已经具备了,这里我们将实现这些扩展能力。限于篇幅,第一部分中所提到的很多扩展的功能,有兴趣的读者可以参考我们的实现完成。

  增加列排序功能

  我们的列排序的接口已经提供了对排序的基本支持,详见图9。

  图9:IField支持排序的接口

  IField支持排序的接口

  compare方法将返回两条数据(Table中的两行在此列的Cell的值)比较后的值来决定这两行的顺序。getDefaultDirection方法将表示该列的排序是升序还是降序。由于设计上的精巧,我们不需要为每个IField实现该接口,我们只需要在AbstractField中实现这两个方法,代码参见清单13,有特殊需要的子类可以重载它们。

  清单13:AbstractField中支持排序的方法

public int compare(Object obj1, Object obj2) {
if (obj1 == null || obj2 == null) {
return 0;
}

if (getValue(obj1) != null&&getValue(obj2)!=null)
return getValue(obj1).compareTo(getValue(obj2));
else
return 0;
}
public int getDefaultDirection() {
return 0;
}

  TreeTableViewer中需要添加清单14中的代码。

  清单14:TreeTableViewer中增加的代码

private TreeTableSorter tableSorter;

protected void initSorters() {
if (fields.length > 0) {
tableSorter = new TreeTableSorter(fields[0]);
setSorter(tableSorter);
}
TreeColumn[] columns = getTree().getColumns();
columns[0].addSelectionListener(new RowSelectionListener());
}

private class RowSelectionListener extends SelectionAdapter {

public void widgetSelected(SelectionEvent e) {
TreeColumn column = (TreeColumn) e.widget;
IField sortField = (IField) column.getData();

Tree tree = column.getParent();
tree.setSortColumn(column);

tableSorter.setSortField(sortField);

int direction = tableSorter.getSortDirection();
if (direction == TreeTableSorter.ASCENDING)
tree.setSortDirection(SWT.UP);
else
tree.setSortDirection(SWT.DOWN);

refresh();
}
}

  然后再新建一个TreeTableSorter 类,代码如清单15所示。

  清单15:新建的TreeTableSorter

public class TreeTableSorter extends ViewerSorter {

public static final int ASCENDING = 1;

public static final int DESCENDING = -1;

private IField field;

private int sortDirection = ASCENDING;

public TreeTableSorter(IField field) {
super();
this.field = field;
}

public void setSortField(IField sortField) {
if (this.field == sortField)
sortDirection *= -1;
else {
sortDirection = ASCENDING;
this.field = sortField;
}
}

public IField getSortField() {
return field;
}

public int getSortDirection() {
return sortDirection;
}

public void setSortDirection(int sortDirection) {
this.sortDirection = sortDirection;
}

public int category(Object element) {
return super.category(element);
}
public int compare(Viewer viewer, Object e1, Object e2) {

if (sortDirection == ASCENDING)
return field.compare(e1, e2);

return field.compare(e2, e1);
}

public boolean isSorterProperty(Object element, String property) {
return super.isSorterProperty(element, property);
}
public void sort(Viewer viewer, Object[] elements) {
super.sort(viewer, elements);
}
}

  在TreeTableSorter的compare方法里,我们轻松的调用IField的compare方法就完成了行数据在该列的排序。有过table排序经验的读者一定有过在比较方法区分每一列的数据进行比较,不得不重复的编写那些烦人的“if…else…”代码。而在我们的TreeTableSorter中,每一列的IField可以自己控制排序算法,因此不需要重复针对不同的列做判断,我们只需要为需要排序的列添加代码中的RowSelectionListener就可以使该列具备排序功能(当然在TreeTable情况下,最好只对第一列排序)。需要添加的代码形如: columns[i].addSelectionListener(new RowSelectionListener());

  请注意我们的TreeTableSorter是一劳永逸的方法,当你的构件增加了排序的功能后,你只需要为每一列添加RowSelectionListener就可以自动的实现该列的排序。更为复杂的情况下,你可以为每个列增加一个属性,来决定该列是否可以排序,这样你甚至都不需要对Table本身做任何修改就可以控制每列的排序能力。

  此时运行测试窗口,点击“Name”的表头,行数据,将能按顺序排序,这里我们只对树的第一层进行排序,效果见图10和图11。

  图10:按部门名字升序排列

  按部门名字升序排列

  图11:按部门名字降序排列

  按部门名字降序排列

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章