扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
1. 问题的起源
我们经常困惑于在不同的项目中重复的编写看上去很相似的代码,有时候会感觉自己只是一个体力劳动者,每天重复的编写着看上去很酷的程序。尤其是在用户界面编程中,拷贝粘贴成了家常便饭,那些迷人的SWT界面其实并没有给你带来什么热情。是的,你需要改变,让生活重新回复光彩,找到第一次写出“HelloWord”的美好感觉。本文将向您展示如何实现灵活可扩展的SWT构件,本文所有的代码在Eclipse3.2.1平台上通过测试。
我们从这样一个问题开始,假设我们在一个基于Eclipse的RCP项目中,需要创建一个表格,用于显示员工
图1展示了模型部分的3个类:Department,Manager和Employee,详细的代码参见附件中的源代码。
图1:Tree Table示例的数据模型
构建一个简单的Tree Table
使用SWT和JFace的构件,我们可以很容易的构造出一个展示图1中的模型的Tree Table演示程序,该演示程序主要包含3个类:EmployeeContentProvider和EmployeeLabelProvider,SimpleTestWindow。
如清单1所示,EmployeeContentProvider负责向TreeTable提供内容。
清单1:EmployeeContentProvider的代码。
public class EmployeeContentProvider extends ArrayContentProvider implements ITreeContentProvider { public Object[] getChildren(Object parentElement) { if(parentElement instanceof Manager&((Manager)parentElement).getMembers()!=null) return ((Manager)parentElement).getMembers().toArray(); else if(parentElement instanceof Department) return ((Department)parentElement).getEmployees().toArray(); return new Object[0]; } public Object getParent(Object element) { return null; } public boolean hasChildren(Object element) { return getChildren(element).length>0; } } |
如清单2所示,EmployeeLabelProvider主要负责为每行的每一列显示Image和Text。
清单2:EmployeeLabelProvider的代码。
public class EmployeeLabelProvider extends LabelProvider implements ITableLabelProvider{ public Image getColumnImage(Object element, int columnIndex) { return null; } public String getColumnText(Object element, int columnIndex) { if(element instanceof Department&& columnIndex ==0) return ((Department)element).getName(); else if(element instanceof Employee) { if(columnIndex ==0) return ((Employee)element).getName(); else if (columnIndex ==1) { if(element instanceof Manager) return "Manager"; else return "Employee"; } else if (columnIndex ==2) return ((Employee)element).getTelephone(); else if (columnIndex ==3) return ((Employee)element).getBirthday(); } return ""; } } |
清单3则展示了如何使用EmployeeContentProvider和EmployeeLabelProvider创建一个TreeTable的示例窗口。
清单3:SimpleTestWindow的代码。
public class SimpleTestWindow extends ApplicationWindow { private TableTreeViewer ttv; public SimpleTestWindow() { super(null); } public void run() { setBlockOnOpen(true); open(); Display.getCurrent().dispose(); } protected void configureShell(Shell shell) { super.configureShell(shell); shell.setText("Simple Tree Table Test"); } protected Control createContents(Composite parent) { ttv = new TableTreeViewer(parent); ttv.getTableTree().setLayoutData(new GridData(GridData.FILL_BOTH)); ttv.setContentProvider(new EmployeeContentProvider()); ttv.setLabelProvider(new EmployeeLabelProvider()); ttv.setInput(TestDataGenerator.getTestDepartments()); // Set up the table Table table = ttv.getTableTree().getTable(); new TableColumn(table, SWT.LEFT).setText("Name"); new TableColumn(table, SWT.LEFT).setText("Job Title"); new TableColumn(table, SWT.RIGHT).setText("Phone"); new TableColumn(table, SWT.RIGHT).setText("Birthday"); for (int i = 0, n = table.getColumnCount(); i < n; i++) { table.getColumn(i).pack(); } table.setHeaderVisible(true); table.setLinesVisible(true); parent.pack(); ttv.reveal(ttv.getElementAt(0)); return ttv.getTableTree(); } public static void main(String[] args) { new SimpleTestWindow().run(); } } |
图2展示了运行SimpleTestWindow后所显示的TreeTable的
图2:SimpleTestWindow的运行结果
这样的设计似乎非常的符合MVC设计模式(参见《设计模式:可复用面向对象
虽然上面的示例中SWT的这种设计模式看上去很优美,然而经验使我们对EmployeeLabelProvider中getColumnText方法中的复杂的条件判断还存在疑惑。难道只能这样无止境的判断下去吗?为什么不能直接的操纵每一列的数据呢?对于简单的需求,示例的说法相对容易,这么简洁的代码,你都不需要加任何的注释,然而一旦面对现实世界中纷繁复杂的业务系统,你的代码势必会迅速的膨胀,例如,仅对这个具体的表格来说:
如果有些行的某些列要求显示特殊的图标,你不得不在EmployeeLabelProvider 的getColumnImage方法中重复大量的判断;
如果调整了列之间的顺序,所有判断的地方都需要重新修改;
如果需要为每个不同的列增加不同的编辑功能(如text,Combo,Dialog),你也会增加很多相似的判断;
如果某些列被要求是可以被隐藏的…
如果希望给不同的列增加过滤显示的功能或者为某些列增加排序的功能…
如果某些特定的行或者列需要增加显示背景色…
如果某些行需要增加特定的右键菜单…
如果某些行可以打开特定的编辑器或者对话框…
好吧,这些事情是我们经常会遇见的,我打赌如果你就按部就班的把这些功能实现后你的ContentProvider和LabelProvider中的代码一定混乱不堪,经过岁月的洗礼和需求的频繁变更,我们的代码迅速膨胀,就像Martin Fowler所说,到处散发出“臭味”,有经验的读者一定在项目中见过长达数千行的ContentProvider和LabelProvider类,这些类是如此的庞大以致于后期的维护和修改变得异常的困难。
何况,如果系统中存在数十个这样的不同的复杂的table,尽管你可以通过快速的拷贝,粘贴,重命名和简单的重构在几天内完成数十个表的工作,然而面对需求的变化,事情就变得越发的复杂,你不得不每天在需求的变化中,重复的修改这些数不清的ContentProvider和LabelProvider,是谁让这个世界如此疯狂?我们一定经历过这样疯狂拷贝粘贴的年代,面对很多类似的表格,我们经常会重复的创建无数的table,给他们增加很多相似的功能,当你沾沾自喜的数着自己一天又编写了多少个
是时候了,是时候我们重新开始,创建一个高度可重用的TreeTable构件了,这个构件还要具有很强的扩展能力。为什么要可重用?因为你会不止一次的用到它。为什么要扩展?因为它不一定完全满足你的需求。如果我们编写程序的时候时刻有这样的想法,我相信我们的程序一定会比以前提高很多,永远不要假定你的代码是封闭的,要时刻准备着被重用。
不要认为这样的想法是多余的,笔者对国内外多个大型业务系统有所接触(
首先,从小处看,我们应当保持代码的纯净,不能任何冗余的代码,冗余的即可重用。拷贝粘贴之余,只要我们坚持重构,在代码级别,我们能保持最大的重用性。更为重要的是,在保持了可重用性的同时,我们还集中了变化点,使得代码的维护变得更为容易。
其次,组件级别(UI上称为构件),只要用相似的功能,我们都能精简,将业务上或者技术上相似的功能封装成可重用的组件,你就会更上一层楼。
最高的境界就是框架层次上的,综合同领域内多个系统要面对的共同问题,构建一个可重用的框架,将会无数倍的降低我们构建类似系统的代价。
我们应当树立“编程序即是编框架”的观念,从小处入手,胸怀天下,随时随地重构,
1 创建一个高度可重用的TreeTable构件使得我们批量的构建TreeTable的时候可以节省大量的工作;
2 该构件能够精确控制行和列的显示以及行为;
3 具有高度的可扩展性,便于增加新的功能而不破坏原有的结构。
那么如何开始呢?你一定有过类似的经验,当你遇到无法解决的问题的时候,你会去研究一些开源的软件,开源软件的无私精神使你能够了解到世界上最顶级的代码,学习和研究这些代码会让你获益匪浅。所以让我们从Eclipse中的一些用户界面设计模式开始吧。
2. 从Eclipse中的用户界面设计模式开始
我们将在这一部分研究Eclipse的一些用户界面设计模式,从而了解到如何使用隔离关注的方法精确控制TreeTable 的行和列。
实现列控制器的IField设计模式
现在我们就要寻找一种SWT的设计模式,这个模式要能精确的控制Table中的每一列的行为。 没有问题,Eclipse的内部就使用这样一种设计模式,可以分散控制每个table的每一列的显示。研究Eclipse但源码和API文档,我们很快就发现,ProblemView这个view中引用了很多的IField的接口,当看到图3中IField接口的方法时,我们就了解到这个正是用来控制Table中的每一列的列控制器。
图3: IField接口的方法
IField接口作为Table的列控制器,能够控制每列的列名,列头(Header)的icon,列头的tooltip,列头的tooltip的icon,每个 cell 的 Image 和 Text(getValue方法),并支持cell之间的比较,可以用于排序,我们将稍后详细的介绍该模式的运行原理。
看上去这个是很不错的框架,遗憾的是在Eclipse中这种设计模式似乎并没有流行起来,从源码中我们就可以看见 LogView 就是完全按照我们第一部分的方法纯手工写就的。对于一个简单的 TreeTable,采用简单的方法做没有任何问题。问题在于,在应用系统中,你很难保证需求不会变更,功能不会膨胀,尤其在像在以表格展现为主的RCP应用中,TreeTable的编程 将成为你日常生活的一部分,此时创建一个
因此我们将使用这种模式来构建我们的TreeTable,我们只要在LabelProvider中调用到这些IField的方法,就能精确控制Table的列,那么,如何控制表格行的行为呢?
可用于行 IWorkBenchAdapter是Eclipse JDT中经常用到的一个接口,该接口通常用于不同类型的节点在树中的显示,如我们常见的
图4:IWorkBenchAdapter 的类阶层结构
图4中可以明显看见WorkBenchAdapter类会将每个对象(文件,文件夹,项目)适配包浏览器中的树节点,如果我们想象树中的每个节点代表每一行数据(一个只有一列的表),那么这样一种
3.创建可重用的TreeTable构件
基于以上的研究,我们实现了一个高度可重用的TreeTable构件,限于篇幅,我们只实现了能够说明问题的最关键的
使用IField实现列控制
为了便于说明问题我们创建了一个简单版本的IField接口,参见图5。
图5:简化后的IField接口
同时实现一个
清单4:TreeTableLabelProvider的代码
public class TreeTableLabelProvider extends LabelProvider implements ITableLabelProvider { private IField[] fields; public TreeTableLabelProvider(IField[] fields) { this.fields = fields; } public Image getColumnImage(Object element, int columnIndex) { if (element == null || fields == null || columnIndex < 0 || columnIndex >= fields.length || fields[columnIndex] == null) { return null; } return fields[columnIndex].getImage(element); } public String getColumnText(Object element, int columnIndex) { if (element == null || fields[columnIndex] == null || fields == null || columnIndex < 0 || columnIndex > fields.length) { return ""; } return fields[columnIndex].getValue(element); } } |
我们还实现了一个TreeTableViewer,如清单5所示,TreeTableViewer会根据IField来创建TreeTable的column。
清单5:TreeTableViewer的代码
public class TreeTableViewer extends CheckboxTreeViewer { private IField[] fields; public TreeTableViewer(Tree tree,IField[] fields, ITreeTableAdapterFactory adapterFactory) { super(tree); this.fields =fields; super.setContentProvider(new TreeTableContentProvider(adapterFactory)); super.setLabelProvider(new TreeTableLabelProvider(fields)); createColumns(); } public void setTableInput(Object[] input) { super.setInput(input); } protected void createColumns() { IField[] fields = getFields(); for (int i = 0; i < fields.length; i++) { if (fields[i] != null) { TreeColumn tc = new TreeColumn(getTree(), SWT.NONE); tc.setText(fields[i].getColumnHeaderText()); tc.setWidth(fields[i].getPreferredWidth()); tc.setData(fields[i]); } } } public IField[] getFields() { return fields; } } |
TreeTableViewer 的构造函数中需要一个 ITreeTableAdapterFactory作为参数,这是一个适配器工厂(参见《设计模式:可复用面向对象
清单6:TreeTableContentProvider的代码
public class TreeTableContentProvider extends ArrayContentProvider implements ITreeContentProvider { private ITreeTableAdapterFactory adapterfactory; public TreeTableContentProvider(ITreeTableAdapterFactory adapterfactory) { this.adapterfactory = adapterfactory; } public Object[] getChildren(Object parentElement) { return ((ITreeTableAdapter) adapterfactory.getAdapter(parentElement)) .getChildren(parentElement); } public Object getParent(Object element) { return ((ITreeTableAdapter) adapterfactory.getAdapter(element)) .getParent(element); } public boolean hasChildren(Object element) { return getChildren(element).length > 0; } } |
ITreeTableAdapter的接口很简单,它参考了IworkbenchAdapter接口的
清单7:ITreeTableAdapter的代码
public interface ITreeTableAdapter { public Object[] getChildren(Object o); public Object getParent(Object o); public Color getBackgroundColor(Object element); } |
为什么TreeTableContentProvider一定需要一个ITreeTableAdapterFactory来作为参数构造呢? 我们考虑一些复杂的情况,一个Table的行,可能会有多种类型的控制方式(还记得第二部分的IWorkbenchAdapter有三个实现吗?)因此我们会为每种类型的行数据创建一个特殊的行控制器,ITreeTableAdapterFactory可以为ContentProvider提供多个ITreeTableAdapter以实现不同类别的行数据的控制逻辑可以单独在一个类中实现,有效的隔离了代码,使得应用
清单8:FixedTreeTableAdapterFactory的代码
public class FixedTreeTableAdapterFactory implements ITreeTableAdapterFactory{ private ITreeTableAdapter adapter; public FixedTreeTableAdapterFactory(ITreeTableAdapter adapter){ this.adapter=adapter; } public ITreeTableAdapter getAdapter(Object treeitemdata) { return adapter; } } |
通常的适配器模式,被适配的对象需要实现IAdaptable的接口,显然这里我们不能采用侵入方式的adapter模式。我们的对象模型是纯净的对象模型,因此我们需要适配器工厂模式,通过工厂取得适配器对象。根据对象获取适配器可以更为精确的控制你的程序,例如我们的示例中,如果不同的部门在table中的行为需要严格控制,或者出生年龄在一定区间的员工需要特殊显示,那么我们可以为每种有特定需求的行数据对象建一个适配器,根据判断行数据的属性来找到具体的适配器。相比较IWorkBenchAdapter, ITreeTableAdapter做了一些修改,不需要提供Label和Image,我们的ITreeTableAdapter更
图6:TreeTableContentProvider和ITreeTableAdapter的关系
如图6, 在这样的设计结构下,所有的适配器被适配器工厂统一管理,一旦行数据的模型发生改变,
完整的TreeTable构件
现在,基于以上的设计和分析,我们实现了TreeTable构件,如图7所示,该构件包装了一个使用TableLayout的Tree构件,在该tree的viewer中,我们使用到了特殊的ContentProvider和LabelProvider。如清单9所示,TreeTable使用IField控制table的列行为,使用ITreeTableAdapter控制行行为。虽然看起来比原来的简单实现复杂,但是结构清晰,便于维护和升级,我们可以轻易的增加新的功能而不会破坏良好的程序结构,我们将在第4部分向读者
图7:TreeTable构件的类图
清单9:TreeTable的代码
public class TreeTable { protected Composite parent; protected Tree tree; protected TreeTableViewer treeViewer; protected ViewForm form; private ITreeTableAdapterFactory adapterFactory; public TreeTable( Composite parent,int style,IField[] fields, ITreeTableAdapterFactory adapterFactory) { this.parent=parent; this.adapterFactory=adapterFactory; initTable( parent , style); treeViewer= new TreeTableViewer(tree,fields,adapterFactory); form.setContent(tree); } private void initTable( Composite parent , int style) { form = new ViewForm(parent, style); form.setLayout(new FillLayout()); form.setBorderVisible(false); form.marginHeight = 0; form.marginWidth = 0; GridData gd = new GridData(SWT.FILL,SWT.FILL,true,true); form.setLayoutData(gd); initTree(form,style); } private void initTree(Composite parent, int style) { tree=new Tree(parent,style); tree.setHeaderVisible(true); tree.setLinesVisible(true); tree.setEnabled(true); tree.setLayout(new TableLayout()); } public void setInput(Object[] inputs){ if(treeViewer!=null) treeViewer.setTableInput(inputs); } public Tree getTree() { return tree; } public TreeTableViewer getTreeViewer() { return treeViewer; } } |
本部分的所有代码我们已经打包于附件中的common.reuse.ui_1.0.0.jar。
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: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的构成
从图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支持排序的接口
compare方法将返回两条数据(Table中的两行在此列的Cell的值)比较后的值来决定这两行的顺序。getDefaultDirection方法将表示该列的排序是升序还是降序。由于
清单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:按部门名字降序排列
增加行背景色功能我们还记得ITreeTableAdapter接口的方法getBackgroundColor,如图12所示,该方法就是要返回特定数据行的背景色,目前为止,EmployeeTreeTableAdapter的该方法没有实现具体的逻辑,我们将这里演示如何给特定的行数据增加背景色, 我们需要给EmployeeTreeTableAdapter 增加清单16中的代码。。
图12:ITreeTableAdapter的接口
清单16:EmployeeTreeTableAdapter中增加的代码
private Color depColor=new Color(null, 255,00,00); public Color getBackgroundColor(Object element) { if(element instanceof Department) return depColor; else return null; } |
此外还需要在TreeTable中的setInput方法中做一些修改,代码见清单17。
清单17:TreeTable的代码变更
public void setInput(Object[] inputs){ if(treeViewer!=null) treeViewer.setTableInput(inputs); initColumnBGColorProperty(); } private void initColumnBGColorProperty() { TreeItem[] items = getTree().getItems(); for (int i = 0; i < items.length; i++) { TreeItem item = items[i]; Color rowItemBGColr =adapterFactory.getAdapter(item.getData()).getBackgroundColor(item.getData()); if(rowItemBGColr!=null) item.setBackground(rowItemBGColr); } } |
此时运行测试窗口,我们成功的给部门行增加了红色背景色,
图13:部门行增加背景色的界面效果
因此可以了解到,在这样一种体系结构下,我们的代码可以很容易的扩展去精确控制TreeTable的行为,并且在扩展之后仍然保持很清晰,简洁的体系结构。由于我们集中的控制各个方面的变化,我们的扩展是正交的,功能上互相没有影响,代码上也没有重复。如果此时我们还在使用第一部分的代码,如果你有多个TreeTable的实例,恐怕免不了又要重复的拷贝很多的代码。 我们还可以给TreeTable增加了编辑、过滤、菜单、列顺序设置、列隐藏和平面显示等功能,可以在项目中大大的提高
6. 总结
本文向读者展示了如何编写可重用的用户构件界面,并提供了一个TreeTable的例子,本文的读者可以根据需要自由的扩展,创建功能更为丰富的TreeTable构件。在大型的RCP项目中,系统会中有很多的对象模型,用户界面可能有数十个表格。使用这种可重用的构件,我们可以快速的创建复杂的界面,降低工作量。并且由于
通过本文的一些实践,读者也可以思考一下其他方面的可重用性设计,如果你热爱
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。