虚拟表格和虚拟树
使用虚拟表格和虚拟树提升SWT应用程序性能
摘要
程序员可以使用虚拟表格和虚拟树快速创建拥有大量数据的表格和树,并且可以有效的显示这些数据。本文表述怎样在SWT应用程序中使用虚拟表格和虚拟树。
Beatriz Iaderoza and Grant Gayed, IBM Ottawa Lab
2006年六月五日
表格和树综述
在SWT应用程序中创建表格或树时,每一个条目(行)的表现是基于TableItem 或 TreeItem。如果数据量很大时,条目的创建就很花费时间,而且计算的代价也很高。这在传统的表格和树中确实是个问题。因为它们的条目都是直接创建,这样初始化的过程就相当慢。另外,如果用户不去看所有创建的条目,这种情况是很常见的,那么很多初始化的工作都白费了。
在这种情况下,就要使用虚拟表格和虚拟树。它们中条目的创建是基于需求的。这样就保证了,如果没有被察看,就没有额外的消耗发生。它们的数据生成是在控件的生命周期之上,而不是直接完成。
举一个例子,用虚拟表格来显示查询图书馆数据库的结果就很合适。因为潜在的大数据量结果集和高昂的数据库查询,用非虚拟表格,初始化的过程就非常的慢。但是,虚拟表格就可以表现的很好,因为初始化的只需要创建第一页的条目。在一个拥有优秀的结果队列探索方法的系统的系统中,这些数据就很可能就是最终提供给用户察看的。
下一段讲述怎样使用虚拟表格和虚拟树,提供了代码片断作为例子。虚拟表格部分讲述的概念同样适用虚拟树。
虚拟表格
为了创建虚拟表格,我们必须指定条数的数目,和一个SWT.SetData监听器,用来为提供的条目设置数据。虚拟表格用条目的个数来设置它的垂直滚动条的最大值。在表格的生命周期内该数目可以被改变。
当需要显示那些没有被初始化的条目时,通常是这些数据第一次对用户可见,这些条目将自动被创建,然后传到SWT.SetData监听器来设置数据。条目数据包括条目的所有属性(文字,图片,颜色,选中的状态)。一条条目不是被SWT.SetData监听器,就是被TableItem API,像setText() 初始化。 一但被初始化,它的值就不变了,SWT.SetData也不会再被调用来设置它的值了。唯一的例外是如果用Table的API clear() 清除了条目的内容,监听器可以在需要的时候被调用来设置数据。
下面的例子展示了如果创建和使用一个虚拟表格。第一段程序演示在虚拟表格中创建1000条条目,然后改变一个条目的数据:
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: 在虚拟表格中加入1000条条目, 清除第5条的值。
Lines 2-5:
创建作为放入表格的数据集
Line 6:
使用 SWT.VIRTUAL属性创建表格,如果输入其他属性,如BORDER,那么这些计算这些属性的并集来显示表格。
Lines 7 and 8:
在表格中加入SWT.SetData 监听器。此监听器负责在条目事件被调用时,将数据设置在条目上。
Line 9:
获取TableItem
Lines 10 and 11:
获得条目的序号,设置条目的显示文字。条目的序号可以在事件的index属性中得到(从3.2开始)。这个序号也可以从Table.indexOf(TableItem)计算得到。
Line 14:
设置表格的条目数为10000,表明它能够保持10000条条目。
Lines 15 and 16:
创建一个按钮用来激发事件改变表格的第5个条目。
Lines 19 and 20:
处理按钮的点击事件,在itemStrings数组序号为5的对象更新值,清除表格的第5项。如果这个条目以后再对用户可见的话,SWT.SetData监听器就会到itemStrings数组中取新值,更新给条目的显示。
使用混合方法
Table.getItem(int) 除了使用SWT.SetData 监听器,可以使用典型的TableItem APIs像setText(...), setImage(...) 来设置数据到未初始化的条目上。Table.getItem(int)可以用来获得还没有初始化的TableItem,但是指定的序号不能超过表格的总条目数。一个从外部数据源获取数据的应用,像显示图书馆数据,就可以从中获益。它们可以在SWT.SetData监听器获取数据之前,有选择的在一些条目上设置数据。例如,如果保持每一条数据库连接非常代价非常高昂,那么一次查询获取一块数据比每初始化一个条目就分别创建连接要好的多。
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); |
Listing 2: 从数据库一批装载60条条目
Line 3:
用指定的属性SWT.VIRTUAL创建虚拟表格
Lines 4 and 5:
为表格加入SWT.SetData监听器,它负责当条目的item属性被调用时,在条目的上设置数据。
Lines 7 to 11:
取得条目的序号,按照每页64条的逻辑,计算开始和中止条目的序号。
Lines 12 to 15:
设置当前逻辑页的所有数据,以后再不会利用SWT.SetData监听器刷新这些数据了。
Line 18:
设置表格的条目数为10000,表明表格能够保持10000条条目。
虚拟树
(从 3.2开始)
虚拟树的使用和虚拟表和很相似,只是拥有了子条目。任何一个条目都可以指定条目数,该数目代表指定的条目拥有的子条目数。这样就很容易推迟在子节点上计算和设置数据,直到该节点第一次打开。同样,树和TreeItem都有清除条目API,根节点和非根节点都可以按照指定的序号进行清楚。
程序3 演示一个简单的文件系统浏览器。这里使用虚拟树来推迟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: 一个使用虚拟树的文件系统浏览器
Figure 1: 文件系统浏览器
Line 1:
利用指定属性SWT.VIRTUAL创建虚拟树。
Lines 2 and 3:
获得文件系统的根节点,在树中保存模型数据。
Lines 4 and 5:
在树中添加SWT.SetData 监听器,用来负责设置数据。
Line 7:
取得条目的父亲,父亲条目中存有文件节点。
Lines 10 to 13:
In this case the TreeItem to have its data set is a root-level item, so its File entry is retrieved from the Tree according to its index, and its text is set. The parent-relative index of the item can always be found in the event's index field. TreeItem拥有的数据集是根,所以它的文件节点按照它的序号从树中获得,设置它的文字。此条目的父关系序号可以从事件的index属性中获得。
Lines 15 to 17:
该TreeItem不是根节点,所以它的文件节点从它的父节点获得。
Lines 19 to 22:
如果该条目是目录,则该目录包含的文件系统节点存储在条目数据模型中,以便于用户的察看。
Line 23:
设置条目的字节点数。这样在条目旁边有一个展开图标。如果用户展开该条目,它的相应个数的子节点将被创建。
总结
虚拟表格和虚拟树初始化数据是基于需要的。这样可以降低计算和内存的消耗,在大容量数据集下提供快速的UI的响应度。