虚拟表和树
通过使用虚拟表和树提高SWT应用程序的性能
提要
虚拟表和树允许开发人员快速创建具有大量数据的表(Table)和树(Tree),并且能够有效地进行填充。本文概述了如何在SWT应用程序中使用虚拟表和树。
作者:Beatriz Iaderoza 和 Grant Gayed, IBM渥太华实验室 2006年6月5月
翻译:土豆爸爸 2006年6月13月
原文: 在这里
表和树概述
在SWT应用程序中创建表或树时,每一项(行)由TableItem或TreeItem表示。如果它们的数量非常庞大或它们需要进行大量的计算,那么创建这些项将花费大量时间。这会给传统的表或树带来一个问题,因为它们的项需要预先创建好,从而导致初始化过程非常慢。此外,如果用户并不查看所有的项——对于大的表和树通常是这样,那么初始代价中的大部分都被浪费了。
虚拟表和树非常适合处理这类情况。在虚拟表和树中,只有在需要时才创建项。这确保了不会花费时间和内存处理那些从未被查看过的项,而且表或树的填充散布在控件的整个生命周期内,而不是全部预先填充。
使用虚拟表的一个场景可能是显示图书馆数据库的查询结果。使用非虚拟表时,初始填充时间可能会非常慢,因为可能返回一个巨大的数据集,并且数据库要花费大量时间获取项目的摘要信息。然而,在这种情况下,虚拟表有出色表现,因为初始填充时间只是获取第一页项目的时间。在一个具有良好启发式排队(ranking heuristics)的结果集的系统中,用户基本上只查看这些项目。
下面几节描述了如何使用虚拟表和树,并且提供了示例代码和说明。应当注意的是,虚拟表一节包含的概念同样适用于虚拟树。
虚拟表
为了填充一个虚拟表,你必须指定它包含项目的数量,并且在SWT.SetData上注册监听器。项目数量用于设置表的垂直流动条的最大值,这个值可以在它的生命周期内变化。
当请求一个未初始化的项目时,通常是因为该项目第一次对用户可见,会自动创建一个项目并传递给SWT.SetData的监听器对它的数据进行设置。项目数据由项目的所有属性组成(文本、图片、颜色、勾选状态)。系统可以自由地以任意顺序请求任意项目。项目一旦被初始化——或者通过SWT.SetData监听器,或者通过TableItem API,例如setText(),那么它的值就持久保存,SWT.SetData listener不再被调用设置它的值。唯一例外的情况是,如果使用表格的clear() API清空一个项目时,监听器会在必要时被调用设置它的数据。
让我们看一个简单的例子,其中涉及到创建和使用虚拟表的基本概念。清单1展示了创建具有10000个项目的表,并对已设置数据的项目进行修改:
1 | int COUNT = 10000; |
2 | final String [] itemStrings = new String [COUNT]; |
3 | for (int i = 0; i < COUNT; i++) { |
4 | itemStrings [i] = "item " + i; |
5 | } |
6 | final Table table = new Table(parent, SWT.BORDER | SWT.VIRTUAL); |
7 | table.addListener(SWT.SetData, new Listener() { |
8 | public void handleEvent(Event event) { |
9 | TableItem item = (TableItem)event.item; |
10 | int index = event.index; |
11 | item.setText(itemStrings [index]); |
12 | } |
13 | }); |
14 | table.setItemCount(COUNT); |
15 | Button button = new Button(parent, SWT.PUSH); |
16 | button.setText("Change item at index 5"); |
17 | button.addListener(SWT.Selection, new Listener() { |
18 | public void handleEvent(Event event) { |
19 | itemStrings [5] = "item " + System.currentTimeMillis(); |
20 | table.clear(5); |
21 | } |
22 | }); |
清单1: 向一个虚拟表中添加10000个项目,并且强制清空第5项。
行 2-5:
创建字符串,将用于设置表中项目的数据。
行 6:
创建表。要创建一个虚拟表,需要在它的构造函数的风格参数里指定SWT.VIRTUAL。注意,如果你还要设置表具有其它风格,把这些风格“或”在一起。
行 7 - 8:
向表添加SWT.SetData监听器。每当访问item域时,该监听器负责对项目设置数据。
行 9:
获取TableItem,将对它设置数据。
行 10 - 11:
获取项目的索引并对它设置相应的文本。该项目的索引可在事件的index域中找到(@since 3.2)。或者通过Table.indexOf(TableItem)计算得到。
行 14:
设置的表的项目数据为10000,指明它将有10000个项目。
行 15 - 16:
创建按钮,它将触发对第5个项目的修改。
行 19 - 20:
处理按钮点击事件。将数组itemStrings的索引为5的位置赋一个新值,清空表索引5。如果这个项目后来对用户可见,那么SWT.SetData监听器将被调用,用itemStrings中的新值设置该项目。
采用混合的方式
除了前面所述的SWT.SetData监听器,还可以使用TableItem API设置虚拟表中未初始化的项目的数据,例如setText(...), setImage(...)等等。可以使用Table.getItem(int)获取一个尚未初始化的TableItem,但必须在表指定的项目数量范围之内。一个从外部数据源获取记录的应用程序,例如前面提到的图书馆数据库,可以在SWT.SetData监听器请求它们之前有选择地设置某些项目的数据。例如,如果每次建立数据连接需要花费很大的代价,每次请求一个查询时提取一批记录比按需对每个项目连接一次数据库效果要好。清单2演示了这种方式,一次取64条记录。
1 | final int COUNT = 100000; |
2 | final int PAGE_SIZE = 64; |
3 | final Table table = new Table (shell, SWT.VIRTUAL | SWT.BORDER); |
4 | table.addListener (SWT.SetData, new Listener () { |
5 | public void handleEvent (Event event) { |
6 | TableItem item = (TableItem) event.item; |
7 | int index = event.index; |
8 | int page = index / PAGE_SIZE; |
9 | int start = page * PAGE_SIZE; |
10 | int end = start + PAGE_SIZE; |
11 | end = Math.min (end, table.getItemCount ()); |
12 | for (int i = start; i < end; i++) { |
13 | item = table.getItem (i); |
14 | item.setText ("Item " + i); |
15 | } |
16 | } |
17 | }); |
18 | table.setItemCount (COUNT); |
清单 2: 从数据库加载表的项目,每次64条记录。
行 3:
通过指定SWT.VIRTUAL风格创建一个虚拟表。
行 4 - 5:
向表添加SWT.SetData监听器。每当访问item域时,该监听器负责对项目设置数据。
行 7 - 11:
获取项目的索引,计算它所在页的开始和结束项目的索引。
行 12 - 15:
设置该页的所有项目的数据。以后不再为这些项目调用SWT.SetData监听器。
行 18:
设置的表的项目数据为10000,指明它将有10000个项目。
虚拟树
(@since 3.2)
虚拟树的使用方式与虚拟表类似,只是多了子项目的处理。除了能够指定树的项目数量—— 它表示顶级项目的数量,任意一个项目也可以指定一个数量。这样,就可以很容易地延迟计算和设置子项目的数据,直到用户第一次展开它。同样,Tree和TreeItem也定义了清空项目的API,可以根据索引清空根项目和非根项目。
清单3演示了一个简单的文件系统浏览程序(如图1所示)。它使用一个虚拟树,以延迟TreeItem的创建,只有在用户需要时才访问本地的文件系统。
1 | final Tree tree = new Tree(parent, SWT.VIRTUAL | SWT.BORDER); |
2 | File [] roots = File.listRoots(); |
3 | tree.setData(roots); |
4 | tree.addListener(SWT.SetData, new Listener() { |
5 | public void handleEvent(Event event) { |
6 | TreeItem item = (TreeItem)event.item; |
7 | TreeItem parentItem = item.getParentItem(); |
8 | File file = null; |
9 | if (parentItem == null) { |
10 | /* root-level item */ |
11 | File [] files = (File [])tree.getData(); |
12 | file = files [event.index]; |
13 | item.setText(file.toString()); |
14 | } else { |
15 | File [] files = (File [])parentItem.getData(); |
16 | file = files [event.index]; |
17 | item.setText(file.getName()); |
18 | } |
19 | if (file.isDirectory()) { |
20 | File [] files = file.listFiles(); |
21 | if (files != null) { |
22 | item.setData(files); |
23 | item.setItemCount(files.length); |
24 | } |
25 | } |
26 | } |
27 | }); |
28 | tree.setItemCount(roots.length); |
清单 3: 一个使用虚拟树的文件系统浏览程序
图1: 文件系统浏览程序。
行 1:
通过指定SWT.VIRTUAL风格创建一个虚拟树。
行 2 - 3:
获取文件系统的顶级文件入口,并将它们作为模型数据存储在树中。
行 4 - 5:
向对添加一个SWT.SetData监听器,每当访问item域时,该监听器负责对项目设置数据。
行 6:
获取项目,将对它设置数据。
行 7:
获取项目的父项目,父项目包含了当前的文件入口。
行 10 - 13:
这种情况是TreeItem为顶级项目,所以它的文件入口是根据索引从树中获取,并且设置它的文本。项目相对于父项目的索引可以在事件的index域中找到。
行 15 - 17:
这种情况是TreeItem不是一个顶级项目,所以它的文件入口是根据索引从父项目中获取,并且设置它的文本。
行 19 - 22:
如果项目代表一个目录,那么获取它的子文件系统入口,并且作为项目的模型数据存储在数据中。
行 23:
设置项目子项数量。这将确保在项目旁边显示展开图标,并且当用户展开它时,能够创建正确数量的子项。
总结
虚拟表和虚拟树提供了一个简单的方式来按需填充控件。它将大大减少计算工作量和内在消耗,可以使界面处理大数据集时保持快速响应。
Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.