本文原文链接地址:http://www.amuhouse.com/link/article.asp?id=94
摘要
组装一个表格或者树的小窗口部件(widget)包括创建条目(item)和设置它 们的属性(如-文本,图像等等),然后就由表格或者树负责显示这些条目。这个步骤让条目的创建显得直接并且在视觉上很一致。对于Eclipse 3.2来说,表格和树的客户机程序现在可以自定义绘制它们的条目,使潜在视觉外观具有更宽的范围。这篇文章探究了表格和树的自定义绘制机制。
作者:Grant Gayed,来自IBM Ottawa Lab
2006年9月15日
翻译:Link
2006年10月
原文出处:http://www.eclipse.org/articles/Article-CustomDrawingTableAndTreeItems/customDraw.htm#_tr20E#_tr20E
背景
树和表格是在结构化管理器里面表示数据的强大工具。数据被表示成一些具有诸如文本, 图像,有时候甚至是如多选框一样的交互式控件的属性的条目。典型的,客户机程序创建条目并且设置它们的属性值,在那之后由表格和树来负责显示这些条目。这 种方法让条目的创建显得直接且在视角上很一致,但是很不方便。例如,表格或者树当中的一个条目只能包含一个图像,而且还必须出现在它的文本的前面。假如一 个客户机程序希望条目具有特定的视角外观范围,满足这些希望创建具有自定义外观的条目的客户机程序的最好的方法是允许这些条目可以被绘制。
部分或者全部自定义绘制TableItem和TreeItem的能力已经被添加进Eclipse 3.2中了,这将在这篇文章中得到描述。必须注意的是,所有在这篇文章中提及到的关于表格的内容(content)也能被同样的应用在树中,反之亦然。
自定义绘制事件
自定义绘制表格是以每个单元(cell)为基础的,这里的单元是指处于父表格的一些行和列中的条目的一部分,或者当表格没有列的时候是指整个条目。例如,如果一个表格有两列和三个条目,那么它就有六个单元。
下面的这些Table的事件被定义来提供绘制过程的钩子(hook):
· SWT.MeasureItem:允许客户指定单元内容的坐标。
· SWT.EraseItem:允许客户机程序自定义绘制单元的背景和/或者选中,并且影响单元的前景是否应该被绘制。
· SWT.PaintItem:允许客户机程序自定义绘制或者增大单元的前景和/或者焦点矩形。
为了最小化绘制单元的工作总量,这些事件被配置成反映单元在默认情况下是如何绘制 的。这使得增大单元的默认外观显得很容易而不用绘制整个单元。如果客户机程序没有定义任何一个这些监听器,那么将进行默认的单元绘制过程。接下来的章节将 会仔细地审查每一种这些事件,并提供一些示例代码片段。
SWT.MeasureItem
SWT.MeasureItem是第一个被发送的绘制事件。这个事件给予客户机程序 指定单元内容的宽度和/或者高度的机会。必须注意到的是,内容的大小不需要和单元的大小相等,因为后者有可能包括额外的装饰,如多选框或者树缩排节点 (indentation)等等。这个事件发出的典型的上下文信息包括绘制单元和包装(pack)一个表格列。这个事件可以用下列的域来进行预配置:
· item :条目
· index :将要被测量(measure)的条目的列索引号
· width : 如果表格要自己绘制单元,它将使用的内容的宽度,基于它的文本,图像和多选框
· height :表格将使用的默认内容高度,基于所有它的所有条目的高度
· gc :一个按单元的字体设定的GC,可以很好的用来进行字符串的测量
希望为单元指定不同的内容宽度和/或者高度的应用程序可以通过改变事件的width和height域来实现。监听器可以选择不改度这两个域中的一个或者两个的值。
例1:指定单元的宽度和高度
表1展示了SWT.MeasureItem的用法,在一个没有列的表格中为条目指定了自定义的单元内容宽度和高度。
1
|
Display display = new Display();
|
2
|
Shell shell = new Shell(display);
|
3
|
shell.setBounds(10,10,200,250);
|
4
|
final Table table = new Table(shell, SWT.NONE);
|
5
|
table.setBounds(10,10,150,200);
|
6
|
table.setLinesVisible(true);
|
7
|
for (int i = 0; i < 5; i++) {
|
8
|
new TableItem(table, SWT.NONE).setText("item " + i);
|
9
|
}
|
10
|
table.addListener(SWT.MeasureItem, new Listener() {
|
11
|
public void handleEvent(Event event) {
|
12
|
int clientWidth = table.getClientArea().width;
|
13
|
event.height = event.gc.getFontMetrics().getHeight() * 2;
|
14
|
event.width = clientWidth * 2;
|
15
|
}
|
16
|
});
|
17
|
shell.open();
|
18
|
while (!shell.isDisposed()) {
|
19
|
if (!display.readAndDispatch()) display.sleep();
|
20
|
}
|
21
|
display.dispose();
|
|
|
表1. 在一个没有列的表格中为条目指定了自定义的单元内容宽度和高度
1-3行:
创建了一个display和shell,并且设置了shell的边界。
创建了一个display和shell,并且设置了shell的边界。
4-6行:
创建一个没有列的表格,设置它的边界,以及设置它的表格线为可见。
创建一个没有列的表格,设置它的边界,以及设置它的表格线为可见。
7-9行:
创建表格的条目。
创建表格的条目。
10-11行:
向表格增加一个SWT.MeasureItem监听器,每当需要单元内容的大小的时候就会被调用。
向表格增加一个SWT.MeasureItem监听器,每当需要单元内容的大小的时候就会被调用。
12行:
得到表格的客户区宽度,这将会在指定单元的内容宽度的时候使用到。
得到表格的客户区宽度,这将会在指定单元的内容宽度的时候使用到。
13行:
设置事件的height域来加倍字体的高度。这有效地加倍了事件中的item域所指定的条目的默认的高度。
设置事件的height域来加倍字体的高度。这有效地加倍了事件中的item域所指定的条目的默认的高度。
14行:
设置事件的width域来加倍表格的宽度。这指定了单元的宽度必须是这个值而不管单元的内容为何。注意到因为表格没有列,单元的宽度就与整个条目的宽度相等了。
设置事件的width域来加倍表格的宽度。这指定了单元的宽度必须是这个值而不管单元的内容为何。注意到因为表格没有列,单元的宽度就与整个条目的宽度相等了。
17-21行:
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
图1展示了表1中的代码片段运行后的屏幕截图。注意到条目高度的增加(由第13行引起),还有列宽度的增加(由第14行引起)都可以从表格的水平滚动条上推知。图2展示了相同的代码段在注释了第10到16行的代码后再运行所得到的屏幕截图,这允许表格以默认的方法绘制。
图2. 表1中的代码在没有运用SWT.MeasureItem监听器时运行的屏幕截图
图3. 表2中的代码运行后,表格的列都已经用SWT.MeasureItem监听器中指定的单元内容大小进行包装后的屏幕截图
图5. 表3运行后,用SWT.EraseItem监听器来绘制一个自定义选中的屏幕截图
图6. 表4中的代码运行后,使用SWT.EraseItem监听器来绘制自定义的条目背景的屏幕截图
图8. 表6中的代码运行后的屏幕截图,使用了SWT.PaintItem监听器来自定义绘制表格条目
图1. 表1中代码运行的屏幕截图,为表格中的条目指定了自定义的宽度和高度
图2. 表1中的代码在没有运用SWT.MeasureItem监听器时运行的屏幕截图
注意到,改变一个单元的SWT.MeasureItem事件的宽度或者高度受到以下的限制:
1. 在Eclipse 3.2中,单元的高度不允许收缩,只允许伸长。
2. 表格中所有的条目具有相同的高度,因此增加一个单元的高度会导致所有表格中的条目的高度也相应的增加。
3. 如果一个单元是在表格的列中的,那么它的宽度由列的宽度来决定。但是,SWT.MeasureItem事件中的width域还是需要总是设置成单元所希望设置的内容宽度,因为这个值当表格没有列,或者当它的列被包装(pack)的时候会被使用到。
例2:包装(pack)列
表2展示了SWT.MeasureItem的用法,指定了当表格列被包装的时候列单元的宽度。
1
|
Display display = new Display();
|
2
|
Shell shell = new Shell(display);
|
3
|
shell.setBounds(10,10,400,200);
|
4
|
Table table = new Table(shell, SWT.NONE);
|
5
|
table.setBounds(10,10,350,150);
|
6
|
table.setHeaderVisible(true);
|
7
|
table.setLinesVisible(true);
|
8
|
final TableColumn column0 = new TableColumn(table, SWT.NONE);
|
9
|
column0.setWidth(100);
|
10
|
final TableColumn column1 = new TableColumn(table, SWT.NONE);
|
11
|
column1.setWidth(100);
|
12
|
column0.addListener(SWT.Selection, new Listener() {
|
13
|
public void handleEvent(Event event) {
|
14
|
column0.pack();
|
15
|
}
|
16
|
});
|
17
|
column1.addListener(SWT.Selection, new Listener() {
|
18
|
public void handleEvent(Event event) {
|
19
|
column1.pack();
|
20
|
}
|
21
|
});
|
22
|
for (int i = 0; i < 5; i++) {
|
23
|
TableItem item = new TableItem(table, SWT.NONE);
|
24
|
item.setText(0, "item " + i + " col 0");
|
25
|
item.setText(1, "item " + i + " col 1");
|
26
|
}
|
27
|
table.addListener(SWT.MeasureItem, new Listener() {
|
28
|
public void handleEvent(Event event) {
|
29
|
event.width *= 2;
|
30
|
}
|
31
|
});
|
32
|
shell.open();
|
33
|
while (!shell.isDisposed()) {
|
34
|
if (!display.readAndDispatch()) display.sleep();
|
35
|
}
|
36
|
display.dispose();
|
表2. 指定了自定义的单元内容宽度,这将被TableColumn.pack()用来设置列的大小
1-3行:
创建了一个display和shell,并且设置了shell的边界。
创建了一个display和shell,并且设置了shell的边界。
4-7行:
创建一个表格,设置它的边界,以及设置它的表格头和表格线为可见。
创建一个表格,设置它的边界,以及设置它的表格头和表格线为可见。
8-11行:
创建两个表格列,设置每一个的初始宽度为100个象素。
创建两个表格列,设置每一个的初始宽度为100个象素。
12-21行:
为每一个表格列增加一个SWT.Selection监听事件。当这些列的列头被点击的时候,这些事件会在列上调用pack()。
为每一个表格列增加一个SWT.Selection监听事件。当这些列的列头被点击的时候,这些事件会在列上调用pack()。
22-26行:
创建表格条目。
创建表格条目。
27-28行:
向表格增加一个SWT.MeasureItem监听器,每当需要单元内容的大小的时候就会被调用。
向表格增加一个SWT.MeasureItem监听器,每当需要单元内容的大小的时候就会被调用。
29行:
设置事件的width域来加倍默认单元的宽度。这指定了单元的宽度必须是这个值而不管单元的内容为何。
设置事件的width域来加倍默认单元的宽度。这指定了单元的宽度必须是这个值而不管单元的内容为何。
32-36行:
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
图3展示了表2中的代码片段运行后,每一个表格列都已经通过点击它们的列头而被包装 之后的屏幕截图。注意到,因为第29行中的SWT.MeasureItem监听器,列已经被包装而具有了两倍所需的宽度。图4展示了相同的代码在注释了第 27到第31行后运行的屏幕截图,这让表格列包装成它们默认的宽度。
图3. 表2中的代码运行后,表格的列都已经用SWT.MeasureItem监听器中指定的单元内容大小进行包装后的屏幕截图
图4. 表2中的代码运行后,表格列都已经用默认的单元大小包装后的屏幕截图
SWT.EraseItem
SWT.EraseItem在单元背景即将被绘制的时候发送。背景由单元的背景颜色或者,如果条目被选择的话,选择背景组成。这个事件允许用户机程序自定义绘制两者中的一个或者两个。同样的,这个事件允许客户机程序指出单元的默认背景是否需要随着背景的绘制也被绘制出来。
当这个事件被接收到的时候,单元已经被或者是表格的背景颜色,或者是它与单元相交的背景图像的一部分所填充。这个事件可以用下列的域来进行预配置:
· item :条目
· index :正在绘制的条目的列索引
· x, y: 单元左上角在与表格相关的坐标系中的坐标值
· width, height :整个单元的宽度和高度,或者当如果表格是没有列的时候是内容的宽度,如果这些值已经被SWT.MeasureItem监听器设置过了,它们在这里是相同的
· gc :一个为单元按默认的前景,背景,字体和剪贴板设定的GC
· detail :下面的比特位中的一个或者多个逻辑或,指示默认情况下什么会被绘制:
o SWT.FOREGROUND :如果当背景已经被绘制时,默认的单元前景将被绘制,这个比特位会被设置(默认值为true)
o SWT.BACKGROUND :如果一个单元特定的(cell-specific)或者条目特定的(item-specific)背景颜色将被为这个单元绘制,这个比特位将被设置(例 如:一种颜色已经被事先通过TableItem.setBackground(Color) 或者TableItem.setBackground(int, Color)设定)
o SWT.SelectED :如果选中会被绘制,这个比特位会被设置,暗示这个条目被选中。
o SWT.FOCUSED :如果聚焦会被绘制,这个比特位会被设置,暗示这个条目在表格中是聚焦条目。
· doit :这个布尔值指出,当这个监听器完成时,表格是否会进行detail域所指定的绘制。(默认值是true)
这个监听器负责修改事件以指定将会被自定义绘制的元素(如果有的话),然后做绘制的工作。这通过清除detail域中的某些比特位来实现,或者通过设置doit域为false来指出监听器会为单元进行所有的绘制(通常结合了SWT.PaintItem监听器)。
例3:自定义绘制条目选中
表3展示了SWT.EraseItem的用法,自定义绘制了表格中的条目选中矩形区域。
1
|
Display display = new Display();
|
2
|
Shell shell = new Shell(display);
|
3
|
final Color red = display.getSystemColor(SWT.COLOR_RED);
|
4
|
final Color yellow = display.getSystemColor(SWT.COLOR_YELLOW);
|
5
|
final Table table = new Table(shell, SWT.FULL_SelectION);
|
6
|
table.setHeaderVisible(true);
|
7
|
new TableColumn(table, SWT.NONE).setWidth(100);
|
8
|
new TableColumn(table, SWT.NONE).setWidth(100);
|
9
|
new TableColumn(table, SWT.NONE).setWidth(100);
|
10
|
for (int i = 0; i < 5; i++) {
|
11
|
TableItem item = new TableItem(table, SWT.NONE);
|
12
|
item.setText(0, "item " + i + " col 0");
|
13
|
item.setText(1, "item " + i + " col 1");
|
14
|
item.setText(2, "item " + i + " col 2");
|
15
|
}
|
16
|
table.pack();
|
17
|
table.addListener(SWT.EraseItem, new Listener() {
|
18
|
public void handleEvent(Event event) {
|
19
|
if ((event.detail & SWT.SelectED) == 0) return; /* item not selected */
|
20
|
int clientWidth = table.getClientArea().width;
|
21
|
GC gc = event.gc;
|
22
|
Color oldForeground = gc.getForeground();
|
23
|
Color oldBackground = gc.getBackground();
|
24
|
gc.setForeground(red);
|
25
|
gc.setBackground(yellow);
|
26
|
gc.fillGradientRectangle(0, event.y, clientWidth, event.height, false);
|
27
|
gc.setForeground(oldForeground);
|
28
|
gc.setBackground(oldBackground);
|
29
|
event.detail &= ~SWT.SelectED;
|
30
|
}
|
31
|
});
|
32
|
shell.pack();
|
33
|
shell.open();
|
34
|
while (!shell.isDisposed()) {
|
35
|
if (!display.readAndDispatch()) display.sleep();
|
36
|
}
|
37
|
display.dispose();
|
表3. 自定义绘制表格选中
1-2行:
创建了一个display和shell。
创建了一个display和shell。
3-4行:
取得系统的红色和黄色两种颜色,它们将被用来绘制自定义选中。
取得系统的红色和黄色两种颜色,它们将被用来绘制自定义选中。
5-6行:
创建一个表格并且设置它的表头为可见。表格的样式被指定为SWT.FULL_SelectION,让自定义绘制 选中可以延伸到整个表格宽度。
创建一个表格并且设置它的表头为可见。表格的样式被指定为SWT.FULL_SelectION,让自定义绘制 选中可以延伸到整个表格宽度。
7-9行:
创建三个表格列并且设置它们每一个的初始化的宽度为100个像素。
创建三个表格列并且设置它们每一个的初始化的宽度为100个像素。
10-15行:
创建表格的条目。
创建表格的条目。
16行:
包装表格到合适的大小。
包装表格到合适的大小。
17-18行:
为表格增加一个SWT.EraseItem监听器,每当单元的背景将被绘制的时候都会被调用。
为表格增加一个SWT.EraseItem监听器,每当单元的背景将被绘制的时候都会被调用。
19行:
检查detail域中是否设置了SWT.SelectED比特位,如果没有设置,跳出监听器函数,因为没有选中将要被绘制。
检查detail域中是否设置了SWT.SelectED比特位,如果没有设置,跳出监听器函数,因为没有选中将要被绘制。
20行:
得到表格的客户区宽度,这将被用来绘制单元选中。
得到表格的客户区宽度,这将被用来绘制单元选中。
21-23行:
从事件中取得GC以开始绘制,并且存储它的前景和背景颜色以便在后面恢复它们。
从事件中取得GC以开始绘制,并且存储它的前景和背景颜色以便在后面恢复它们。
24-26行:
用从红色到黄色延伸的梯度颜色绘制自定义选中的矩形区域。第26行指定整个条目的宽度为梯度的边界因此颜色范围将会适当地延伸到表格的宽度。因为GC的剪贴板已经被预先定义为单元的边界,只有这个梯度的这一部分的绘制会出现。
用从红色到黄色延伸的梯度颜色绘制自定义选中的矩形区域。第26行指定整个条目的宽度为梯度的边界因此颜色范围将会适当地延伸到表格的宽度。因为GC的剪贴板已经被预先定义为单元的边界,只有这个梯度的这一部分的绘制会出现。
27-28行:
恢复GC的前景和背景颜色为之前的值。
恢复GC的前景和背景颜色为之前的值。
29行:
从事件的detail域中清空 SWT.SelectED比特位以指明默认的选中不能被为这个单元绘制出来。注意到这个条目仍然被当然在表格中逻辑上被选中的。
从事件的detail域中清空 SWT.SelectED比特位以指明默认的选中不能被为这个单元绘制出来。注意到这个条目仍然被当然在表格中逻辑上被选中的。
32-37行:
包装并且打开shell,,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
包装并且打开shell,,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
图5展示了表3中的代码片段运行后屏幕截图。虽然选中梯度看起来是一个连续的矩形区域,但是事实上是分三次来绘制的,每一次都剪贴了相应的单元。
图5. 表3运行后,用SWT.EraseItem监听器来绘制一个自定义选中的屏幕截图
例4:自定义绘制单元背景
表4展示了SWT.EraseItem的用法,自定义绘制了一个没有列的表格的单元背景。这个例子修改了默认的GC剪贴板来显示表示温度范围的梯度矩形区域。
1
|
final String[] MONTHS = {
|
2
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
3
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
4
|
}
|
5
|
final int[] HIGHS = {-7, -4, 1, 11, 18, 24, 26, 25, 20, 13, 5, -4};
|
6
|
final int[] LOWS = {-15, -13, -7, 1, 7, 13, 15, 14, 10, 4, -2, -11};
|
7
|
final int SCALE_MIN = -30; final int SCALE_MAX = 30;
|
8
|
final int SCALE_RANGE = Math.abs(SCALE_MIN - SCALE_MAX);
|
9
|
Display display = new Display();
|
10
|
Shell shell = new Shell(display);
|
11
|
shell.setBounds(10,10,400,350);
|
12
|
shell.setText("Ottawa Average Daily Temperature Ranges");
|
13
|
final Color blue = display.getSystemColor(SWT.COLOR_BLUE);
|
14
|
final Color white = display.getSystemColor(SWT.COLOR_WHITE);
|
15
|
final Color red = display.getSystemColor(SWT.COLOR_RED);
|
16
|
final Image parliamentImage = new Image(display, "./parliament.jpg");
|
17
|
final Table table = new Table(shell, SWT.NONE);
|
18
|
table.setBounds(10,10,350,300);
|
19
|
table.setBackgroundImage(parliamentImage);
|
20
|
for (int i = 0; i < 12; i++) {
|
21
|
TableItem item = new TableItem(table, SWT.NONE);
|
22
|
item.setText(MONTHS[i] + " (" + LOWS[i] + "C..." + HIGHS[i] + "C)");
|
23
|
}
|
24
|
final int clientWidth = table.getClientArea().width;
|
25
|
table.addListener(SWT.MeasureItem, new Listener() {
|
26
|
public void handleEvent(Event event) {
|
27
|
int itemIndex = table.indexOf((TableItem)event.item);
|
28
|
int rightX = (HIGHS[itemIndex] - SCALE_MIN) * clientWidth / SCALE_RANGE;
|
29
|
event.width = rightX;
|
30
|
}
|
31
|
});
|
32
|
table.addListener(SWT.EraseItem, new Listener() {
|
33
|
public void handleEvent(Event event) {
|
34
|
int itemIndex = table.indexOf((TableItem)event.item);
|
35
|
int leftX = (LOWS[itemIndex] - SCALE_MIN) * clientWidth / SCALE_RANGE;
|
36
|
int rightX = (HIGHS[itemIndex] - SCALE_MIN) * clientWidth / SCALE_RANGE;
|
37
|
GC gc = event.gc;
|
38
|
Rectangle clipping = gc.getClipping();
|
39
|
clipping.x = leftX;
|
40
|
clipping.width = rightX - leftX;
|
41
|
gc.setClipping(clipping);
|
42
|
Color oldForeground = gc.getForeground();
|
43
|
Color oldBackground = gc.getBackground();
|
44
|
gc.setForeground(blue);
|
45
|
gc.setBackground(white);
|
46
|
gc.fillGradientRectangle(
event.x, event.y, event.width / 2, event.height, false); |
47
|
gc.setForeground(white);
|
48
|
gc.setBackground(red);
|
49
|
gc.fillGradientRectangle(
event.x + event.width / 2, event.y, event.width / 2, event.height, false); |
50
|
gc.setForeground(oldForeground);
|
51
|
gc.setBackground(oldBackground);
|
52
|
event.detail &= ~SWT.BACKGROUND;
|
53
|
}
|
54
|
});
|
55
|
shell.open();
|
56
|
while (!shell.isDisposed()) {
|
57
|
if (!display.readAndDispatch()) display.sleep();
|
58
|
}
|
59
|
parliamentImage.dispose();
|
60
|
display.dispose();
|
表4. 自定义绘制条目的背景
1-8
行
:
定义将要用来设置条目数据(data)和用来绘制条目背景的常量。
定义将要用来设置条目数据(data)和用来绘制条目背景的常量。
9-12
行
:
创建一个display和shell,并且设置shell的边界和标题。
创建一个display和shell,并且设置shell的边界和标题。
13-15
行
:
得到系统的蓝色,白色和红色,这些颜色将被用来绘制条目的背景。
得到系统的蓝色,白色和红色,这些颜色将被用来绘制条目的背景。
16
行
:
载入一个图像,它将被用来作为表格的背景。
载入一个图像,它将被用来作为表格的背景。
17-18
行
:
创建没有列的表格,设置它的边界。
创建没有列的表格,设置它的边界。
19
行
:
设置表格的背景图像。
设置表格的背景图像。
20-23
行
:
创建表格条目。
创建表格条目。
24
行
:
保存表格的客户区宽度以便在以后绘制表格的条目背景时使用。
保存表格的客户区宽度以便在以后绘制表格的条目背景时使用。
25-26
行
:
为表格增加一个SWT.MeasureItem监听器让条目的大小可以被指定。
为表格增加一个SWT.MeasureItem监听器让条目的大小可以被指定。
27-29
行
:
计算事件的item域指定的条目的左边边界,并且设置事件的width为这个值。
计算事件的item域指定的条目的左边边界,并且设置事件的width为这个值。
32-33行:
为表格增加一个SWT. EraseItem监听器让条目的背景可以被自定义绘制。
为表格增加一个SWT. EraseItem监听器让条目的背景可以被自定义绘制。
34-36
行
:
从事件的item域得到条目,计算它的左边和右边边界,让它的温度条能够根据它们来绘制。
从事件的item域得到条目,计算它的左边和右边边界,让它的温度条能够根据它们来绘制。
37-41
行
:
修改gc域中指定的GC的剪贴板为这些为条目的温度条计算出来的边界。
修改gc域中指定的GC的剪贴板为这些为条目的温度条计算出来的边界。
42-43
行
:
保存GC的前景和背景颜色,以便以后恢复它们时使用。
保存GC的前景和背景颜色,以便以后恢复它们时使用。
44-46
行
:
用从蓝到白延伸的梯度绘制左半边自定义背景矩形区域。这个矩形区域与在第41行设置的GC剪贴板区域相交的一部分将会最终显现出来。
用从蓝到白延伸的梯度绘制左半边自定义背景矩形区域。这个矩形区域与在第41行设置的GC剪贴板区域相交的一部分将会最终显现出来。
47-49
行
:
用从白到红延伸的梯度绘制右半边自定义背景矩形区域。这个矩形区域与在第41行设置的GC剪贴板区域相交的一部分将会最终显现出来。
用从白到红延伸的梯度绘制右半边自定义背景矩形区域。这个矩形区域与在第41行设置的GC剪贴板区域相交的一部分将会最终显现出来。
50-51
行
:
恢复GC的前景和背景颜色为先前的值。
恢复GC的前景和背景颜色为先前的值。
52行:
从事件的detail域中清除SWT.BACKGROUND比特位以指定不应该为这个单元绘制默认的背景。
从事件的detail域中清除SWT.BACKGROUND比特位以指定不应该为这个单元绘制默认的背景。
55-60行:
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
图6展示了表4中的代码片段运行后的屏幕截图。
图6. 表4中的代码运行后,使用SWT.EraseItem监听器来绘制自定义的条目背景的屏幕截图
SWT.PaintItem
SWT.PaintItem在单元的默认前景内容刚好被绘制完毕的时候发送。这个事件允许客户机程序增大单元,或者完全绘制单元的内容。这个事件可以用下列的域来进行预配置:
· item :条目
· index :正在绘制的条目的列索引
· x, y: 单元内容(如,它的图像或者文本)左上角在与表格相关的坐标系中的坐标值
· width, height :单元内容的宽度和高度,或者当如果表格是没有列的时候是内容的宽度,如果这些值已经被SWT.MeasureItem监听器设置过了,它们在这里是相同的
· gc :一个为单元按默认的前景,背景,字体和剪贴板设定的GC
· detail: 下面这些比特位的零个或者一个的逻辑或:
o SWT.SelectED: 如果条目被选中,这个比特位就会被设置
o SWT.FOCUSED: 如果这个条目是表格的聚焦条目,这个比特位就会被设置
X和y域的目的是为了指明自定义绘制的区域。这两个值把诸如多选框和树缩排结点也计算在内。
例5:增强本地内容
表5展示了SWT.PaintItem的用法,增强了默认的树条目的绘制。它使用SWT.PaintItem 事件中的x, y 和width域来在可选条目的右侧绘制一个图像。这只是一个简单的绘制增强的例子,它在保持条目的本地外观的同时增强了条目的默认内容。
1
|
Display display = new Display();
|
2
|
Shell shell = new Shell(display);
|
3
|
shell.setBounds(10, 10, 350, 200);
|
4
|
Image itemImage = new Image(display, "./palette.gif");
|
5
|
Image xImage = new Image(display, "./x.gif");
|
6
|
final int IMAGE_MARGIN = 2;
|
7
|
final Tree tree = new Tree(shell, SWT.CHECK);
|
8
|
tree.setBounds(10, 10, 300, 150);
|
9
|
TreeItem item = new TreeItem(tree, SWT.NONE);
|
10
|
item.setText("root item");
|
11
|
item.setImage(itemImage);
|
12
|
for (int i = 0; i < 4; i++) {
|
13
|
TreeItem newItem = new TreeItem(item, SWT.NONE);
|
14
|
newItem.setText("descendent " + i);
|
15
|
newItem.setImage(itemImage);
|
16
|
if (i % 2 == 0) newItem.setData(xImage);
|
17
|
item.setExpanded(true);
|
18
|
item = newItem;
|
19
|
}
|
20
|
tree.addListener(SWT.MeasureItem, new Listener() {
|
21
|
public void handleEvent(Event event) {
|
22
|
TreeItem item = (TreeItem)event.item;
|
23
|
Image trailingImage = (Image)item.getData();
|
24
|
if (trailingImage != null) {
|
25
|
event.width += trailingImage.getBounds().width + IMAGE_MARGIN;
|
26
|
}
|
27
|
}
|
28
|
});
|
29
|
tree.addListener(SWT.PaintItem, new Listener() {
|
30
|
public void handleEvent(Event event) {
|
31
|
TreeItem item = (TreeItem)event.item;
|
32
|
Image trailingImage = (Image)item.getData();
|
33
|
if (trailingImage != null) {
|
34
|
int x = event.x + event.width + IMAGE_MARGIN;
|
35
|
int itemHeight = tree.getItemHeight();
|
36
|
int imageHeight = trailingImage.getBounds().height;
|
37
|
int y = event.y + (itemHeight - imageHeight) / 2;
|
38
|
event.gc.drawImage(trailingImage, x, y);
|
39
|
}
|
40
|
}
|
41
|
});
|
42
|
shell.open();
|
43
|
while (!shell.isDisposed()) {
|
44
|
if (!display.readAndDispatch()) display.sleep();
|
45
|
}
|
46
|
itemImage.dispose();
|
47
|
xImage.dispose();
|
48
|
display.dispose();
|
表5. 用SWT.PaintItem来增强默认的条目绘制
1-3
行
:
创建一个display和shell,设置shell的边界。
创建一个display和shell,设置shell的边界。
4-6
行
:
加载一个将会在本例中使用的图像,定义将会在绘制条目前景的时候使用的常量。
加载一个将会在本例中使用的图像,定义将会在绘制条目前景的时候使用的常量。
7-8行:
创建没有列的树,设置它的边界。
创建没有列的树,设置它的边界。
9-19
行
:
创建树条目。注意到第15行为每一个条目设置了条目图像,第16行设置了尾随的图像作为条目数据以便改变条目。这个数据将会在自定义绘制条目前景的时候使用。
创建树条目。注意到第15行为每一个条目设置了条目图像,第16行设置了尾随的图像作为条目数据以便改变条目。这个数据将会在自定义绘制条目前景的时候使用。
20-21
行
:
为树增加一个SWT.MeasureItem监听器,让条目的大小可以被指定。
为树增加一个SWT.MeasureItem监听器,让条目的大小可以被指定。
22-26行:
从事件的item域得到条目,并且如果它有尾随的图像,修改事件的width域的值,让这个空间包括条目的默认内容加上尾随图像。
从事件的item域得到条目,并且如果它有尾随的图像,修改事件的width域的值,让这个空间包括条目的默认内容加上尾随图像。
29-30
行
:
为树增加一个SWT.PaintItem监听器,让默认的前景可以被加强。
为树增加一个SWT.PaintItem监听器,让默认的前景可以被加强。
31-39
行
:
从事件的item域中得到树条目,并且如果它有尾随的图像,在条目文本的右侧绘制这个图像。事件的x, y 和 width域被用来计算图像的位置。
从事件的item域中得到树条目,并且如果它有尾随的图像,在条目文本的右侧绘制这个图像。事件的x, y 和 width域被用来计算图像的位置。
42-48
行
:
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
图7显示了表5中的代码片段运行后的屏幕截图。它有本地树的外观,也包括了一个尾承受的图像。
图7. 表5的代码运行后的屏幕截图,使用了SWT.PaintItem监听器来增强树条目的默认绘制
例6.自定义绘制完整的条目内容
表6展示了SWT.PaintItem的用法,绘制了有多行文本的条目,而这目前还不能被表格所支持。它清除了SWT.EraseItem 事件中的detail 域的SWT.FOREGROUND比特位,然后在SWT.PaintItem事件中绘制文本。
1
|
final int COLUMN_COUNT = 4;
|
2
|
final int ITEM_COUNT = 8;
|
3
|
final int TEXT_MARGIN = 3;
|
4
|
Display display = new Display();
|
5
|
Shell shell = new Shell(display);
|
6
|
final Table table = new Table(shell, SWT.FULL_SelectION);
|
7
|
table.setHeaderVisible(true);
|
8
|
table.setLinesVisible(true);
|
9
|
for (int i = 0; i < COLUMN_COUNT; i++) {
|
10
|
new TableColumn(table, SWT.NONE);
|
11
|
}
|
12
|
for (int i = 0; i < ITEM_COUNT; i++) {
|
13
|
TableItem item = new TableItem(table, SWT.NONE);
|
14
|
for (int j = 0; j < COLUMN_COUNT; j++) {
|
15
|
String string = "item " + i + " col " + j;
|
16
|
if ((i + j) % 3 == 1) {
|
17
|
string +="/nnew line1";
|
18
|
}
|
19
|
if ((i + j) % 3 == 2) {
|
20
|
string +="/nnew line1/nnew line2";
|
21
|
}
|
22
|
item.setText(j, string);
|
23
|
}
|
24
|
}
|
25
|
table.addListener(SWT.MeasureItem, new Listener() {
|
26
|
public void handleEvent(Event event) {
|
27
|
TableItem item = (TableItem)event.item;
|
28
|
String text = item.getText(event.index);
|
29
|
Point size = event.gc.textExtent(text);
|
30
|
event.width = size.x + 2 * TEXT_MARGIN;
|
31
|
event.height = Math.max(event.height, size.y + TEXT_MARGIN);
|
32
|
}
|
33
|
});
|
34
|
table.addListener(SWT.EraseItem, new Listener() {
|
35
|
public void handleEvent(Event event) {
|
36
|
event.detail &= ~SWT.FOREGROUND;
|
37
|
}
|
38
|
});
|
39
|
table.addListener(SWT.PaintItem, new Listener() {
|
40
|
public void handleEvent(Event event) {
|
41
|
TableItem item = (TableItem)event.item;
|
42
|
String text = item.getText(event.index);
|
43
|
/* center column 1 vertically */
|
44
|
int yOffset = 0;
|
45
|
if (event.index == 1) {
|
46
|
Point size = event.gc.textExtent(text);
|
47
|
yOffset = Math.max(0, (event.height - size.y) / 2);
|
48
|
}
|
49
|
event.gc.drawText(text, event.x + TEXT_MARGIN, event.y + yOffset, true);
|
50
|
}
|
51
|
});
|
52
|
for (int i = 0; i < COLUMN_COUNT; i++) {
|
53
|
table.getColumn(i).pack();
|
54
|
}
|
55
|
table.pack();
|
56
|
shell.pack();
|
57
|
shell.open();
|
58
|
while (!shell.isDisposed()) {
|
59
|
if (!display.readAndDispatch()) display.sleep();
|
60
|
}
|
61
|
display.dispose();
|
图6. 使用SWT.PaintItem来绘制有多行文本的条目
1-3
行
:
定义创建和绘制表格条目时将要使用到的常量。
定义创建和绘制表格条目时将要使用到的常量。
4-5
行
:
创建一个display和shell。
创建一个display和shell。
6-8
行
:
创建表格,设置它的表格头和表格线为可见。
创建表格,设置它的表格头和表格线为可见。
9-11行:
创建表格列。
创建表格列。
12-24
行
:
创建表格条目。条目文本被设置成具有一行、两行或者三行文本的值。
创建表格条目。条目文本被设置成具有一行、两行或者三行文本的值。
25-26
行
:
为表格增加一个SWT.MeasureItem监听器,让条目大小可以被指定。
为表格增加一个SWT.MeasureItem监听器,让条目大小可以被指定。
27-31行:
从事件得到条目和列索引值,计算它的文本的边界,用这个值设置事件的width和height域。为边界增加TEXT_MARGIN值,以便在条目内容的周围创建一个小的空白。
从事件得到条目和列索引值,计算它的文本的边界,用这个值设置事件的width和height域。为边界增加TEXT_MARGIN值,以便在条目内容的周围创建一个小的空白。
34-38
行
:
为表格增加一个SWT.EraseItem监听器,以便从事件的detail域中清除SWT.FOREGROUND比特位。这指明条目前景的默认绘制不应该发生,因为SWT.PaintItem监听器会完成这个工作。
为表格增加一个SWT.EraseItem监听器,以便从事件的detail域中清除SWT.FOREGROUND比特位。这指明条目前景的默认绘制不应该发生,因为SWT.PaintItem监听器会完成这个工作。
39-40
行
:
为表格增加一个SWT.PaintItem监听器,以使得条目的前景可以被自定义地绘制。
为表格增加一个SWT.PaintItem监听器,以使得条目的前景可以被自定义地绘制。
41-49
行
:
得到单元的文本并且绘制它。在第一列中这个文本被在单元中垂直地居中,在另外的列中则被以顶端对齐的方式绘制。
得到单元的文本并且绘制它。在第一列中这个文本被在单元中垂直地居中,在另外的列中则被以顶端对齐的方式绘制。
52-54
行
:
使用SWT.MeasureItem监听器提供的值,包装表格的列到它们的合适大小。
使用SWT.MeasureItem监听器提供的值,包装表格的列到它们的合适大小。
55-56
行
:
包装表格和shell到它们的合适大小。
包装表格和shell到它们的合适大小。
57-61
行
:
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
打开shell,运行事件循环直到shell被销毁(dispose),并且在退出前销毁display。
图8显示了表6中的代码片段运行后的屏幕截图。
图8. 表6中的代码运行后的屏幕截图,使用了SWT.PaintItem监听器来自定义绘制表格条目
总结
适当地使用表格和树的自定义绘制措施,条目可以显示为本地的,自定义绘制的,或者介乎两者之间的外观。通过一些小小 的努力,客户机程序可以用这些措施来用以它们想要的任何方式显示数据。自定义条目绘制的潜在的应用可以在本文所展示的例子之上进行扩展。为了看到更多的正 在增长的关于知定义绘制表格和树的代码例子,可以访问
http://www.eclipse.org/swt/snippets/#table和
http://www.eclipse.org/swt/snippets/#tree。
关于这篇文章的勘误和注释可以报告到
https://bugs.eclipse.org/bugs/show_bug.cgi?id=157330.