SWT/JFace开发入门指南(四)〈转帖〉
让你的 swt 程序动起来
在向使用者提供最差的用户体验方面,中国的 IT 企业始终走在时代的最前端。之所以有这样的感慨其实是来源于往 blog 上贴上一节的内容:我用了一整天的功夫,不断与 CSDN 各种莫名其妙的出错提示进行斗争,最后终于成功的贴了上去。
其实作为 CSDN blog 一个使用者,我的要求并不高:只要能写 blog ,能够正常访问就可以了。然而就是这么一点基本的要求好像也得不到满足。
我不知道大家有没有这样的体验:其实软件使用者要求的东西都很基本,而现在软件做得越来越复杂,有相当大一部分是在于软件开发者把自己的注意力放在了一些附加功能(这些功能可能让用户感到惊喜,但是如果没有它们用户也不会不满意)上,而真正用户的要求却得不到满足。所以大家在设计程序的时候,一定要明白,有时候简单就是一种美,把时间花费到真正有价值的地方去。
OK ,回到我们的主题上来。在这一节中,我将给大家介绍 swt 的事件模式。在前面我们也提到过,写一个 swt 程序,无非就是分几步走。其中比较需要费心的就是布置好用户界面和处理各种事件。
添加了事件处理的 Hello,world!
其实 swt 中处理事件非常简单,对应于各种事件都有相应的 listener 类,如果一种事件叫做 Xyz ,那么对应的 listener 类就是 XyzListener 。比如对应于鼠标事件的有 MouseListener ,对应于键盘事件的就是 KeyListener 。而在每种 widget 中,对于它可以处理的事件都有 addXyzListener 方法,只要把对应的 listener 实例作为参数传给它就可以了。
为了更加清楚的说明,我们先来看下面一段程序:
2
3 private Shell _shell;
4
5 public EventDemo() {
6 Display display = new Display();
7 Shell shell = new Shell(display,SWT.SHELL_TRIM);
8 setShell(shell);
9 RowLayout layout = new RowLayout();
10 shell.setLayout(layout);
11 shell.setText( " Event demo " );
12
13 Button button = new Button(shell,SWT.PUSH | SWT.CENTER);
14 button.setText( " Click me! " );
15
16 button.addSelectionListener( new SelectionListener(){
17
18 public void widgetSelected(SelectionEvent event ) {
19 handleSelectionEvent();
20 }
21
22 public void widgetDefaultSelected(SelectionEvent event ) {
23 }
24 });
25 shell.setBounds( 200 , 300 , 100 , 100 );
26 shell.open();
27
28 while ( ! shell.isDisposed()) {
29 if ( ! display.readAndDispatch()) {
30 display.sleep();
31 }
32 }
33 display.dispose();
34
35 }
36
37 protected void handleSelectionEvent() {
38 MessageBox dialog = new MessageBox(getShell(),SWT.OK | SWT.ICON_INFORMATION);
39 dialog.setText( " Hello " );
40 dialog.setMessage( " Hello,world! " );
41 dialog.open();
42 }
43
44 /* *
45 * @param args
46 */
47 public static void main(String[] args) {
48
49 EventDemo eventdemo = new EventDemo();
50 }
51
52 /* *
53 * @return Returns the _shell.
54 */
55 public Shell getShell() {
56 return _shell;
57 }
58
59 /* *
60 * @param _shell The _shell to set.
61 */
62 public void setShell(Shell shell) {
63 this ._shell = shell;
64 }
65 }
66
代码段 6
你可以看到在这段程序中,我们只创建了一个 Button ,随后调用了它的 addSelectionListener() 方法,在这个新创建的 Listener ,我们只为 widgetSelected 方法添加了代码,并在其中创建了一个对话框。实际运行效果如下图,其中那个标有 Hello,world 的对话框是按了按钮以后出现的:
图 6
如果总结一下,我们可以得出处理事件的几个步骤:
1. 针对你所处理的事件,找出合适的 XyzListener 接口
2. 编写一个新的类,这个类实现了 XyzListener 接口
3. 在你所感兴趣的事件中编写处理代码,而对于那些你不感兴趣的方法可以让它们保持空白(就像实例中的 widgetDefaultSelected() 方法)一样
让事件处理更加简单:使 用适配器( adapter )
有时候我们可能会感觉这样仍然不够简单,比如我只对 SelectionListener 中的 widgetSelected() 方法感兴趣,但是为了能够通过编译器的编译,我却不得不写一个空白的 widgetDefaultSelected() 方法(因为 SelectionListener 是一个接口,你必须实现它所有的方法)。
幸运的是, swt 帮我们解决了这个问题,途径就是使用 adapter 。在 swt 中,对应于一个 XyzListener 都有一个 XyzAdapter , adapter 都是抽象类并且实现了对应的 listener 接口,它为对应 listener 接口中的每个方法都定义了一个默认实现(基本上就是什么都不做),我们在使用时候只需要 override 掉自己感兴趣的方法就可以了。
结合上一小节中的代码,如果使用 SelectionAdapter 代替 SelectionListener 的话,我们的代码就可以这样写:
public void widgetSelected(SelectionEvent event ) {
handleSelectionEvent();
}
});
这样是不是很方便呢?
EventObject : 事件处理的附加信息
在处理各种事件时,我们需要一些附加信息,而 EventObject 给我们提供了这些信息。
我们先来看下面这个简单的小程序,在这段程序中,我们创建了两个文本框,当在第一个文本框输入时,第二个文本框中显示输入的字符。
2
3 Text logText;
4
5 public EventDemo2() {
6 Display display = new Display();
7 Shell shell = new Shell(display,SWT.SHELL_TRIM);
8
9 GridLayout layout = new GridLayout();
10 layout.numColumns = 2 ;
11 shell.setLayout(layout);
12 shell.setText( " Event demo " );
13
14 Label label1 = new Label(shell,SWT.RIGHT);
15 label1.setText( " text1: " );
16 Text text1 = new Text(shell,SWT.NONE);
17
18 text1.addKeyListener( new KeyAdapter(){
19 public void keyPressed(KeyEvent e) {
20 Text t = getLogText();
21 String s = t.getText();
22 t.setText(String.valueOf(e.character));
23 }
24 }
25 );
26
27 Label label2 = new Label(shell,SWT.RIGHT);
28 label2.setText( " text2: " );
29 Text text2 = new Text(shell,SWT.NONE);
30 text2.setEditable( false );
31 text2.setBackground( new Color(display, 255 , 255 , 255 ));
32 setLogText(text2);
33
34 shell.pack();
35 shell.open();
36
37 while ( ! shell.isDisposed()) {
38 if ( ! display.readAndDispatch()) {
39 display.sleep();
40 }
41 }
42 display.dispose();
43 }
44 /* *
45 * @param args
46 */
47 public static void main(String[] args) {
48 EventDemo2 demo2 = new EventDemo2();
49 }
50 /* *
51 * @return Returns the logText.
52 */
53 public Text getLogText() {
54 return logText;
55 }
56 /* *
57 * @param logText The logText to set.
58 */
59 public void setLogText(Text logText) {
60 this .logText = logText;
61 }
62 }
63
代码段 7
你可能没有兴趣仔细研究这么长的代码,那么让我们只关注这一小段代码:
public void keyPressed(KeyEvent e) {
Text t = getLogText();
String s = t.getText();
t.setText(String.valueOf(e.character));
}
}
);
在这段代码中,我们使用了 KeyAdapter 来处理键盘事件,而 keyPressed 会在有键按下时候被调用,我们在函数中使用了 KeyEvent 类型的参数 e ,并且通过 e.character 得到了按下键对应的字符。
各种 EventObject (例如上面示例中的 KeyEvent )在事件处理函数中作为参数出现,它们可能有不同的属性和方法,利用这些特性我们可以做很多有意思的事情。
我们下面只简单介绍几种 EventObject ,它们分别是对应于窗口事件( ShellListener , ShellAdapter )的 ShellEvent ,对应于键盘事件 (KeyListener , KeyAdapter) 的 KeyEvent 和对应于鼠标事件( MouseListener , MouseAdapter )的 MouseEvent 。希望可以起到窥一斑而见全豹的作用。
几种 EventObject 简介
ShellEvent
如果你打开 ShellEvent 的 API ,你会很惊讶的发现它只有一个布尔型的属性,就是 doit 。这个莫名其妙的属性是用来做什么的呢?
我们知道, Shell 对应的就是程序的窗口,在 ShellListener 中定义的几种事件包括窗口激活时候的 shellActivated ,窗口即将被关闭时候的 shellClosed 等等。 ShellEvent 中唯一的属性 doit ,就是用来设定是否这些动作将有效的。
再说得具体一些,比如 Windows 下通常我们会通过点击窗口右上角的关闭按钮来关闭窗口,这个时候就会对 shellClosed 进行调用,如果我们在 shellClosed (ShellEvent e) 方法中把 ShellEvent 对象 e 的 doit 属性置为了 false ,那么这次动作就无效,窗口不会被关闭。
在有些其他的 EventObject 中也有 doit 属性,它们的作用都是类似的。比如 KeyEvent 就有这样的一个属性。如果你在 keyPressed 方法中把它置为了 false ,就等于你按键盘(对于对应的 widget ,也就是 receiver 来讲)没有用。
KeyEvent
其实在前面我们或多或少的已经介绍了一些 KeyEvent 的知识。 KeyEvent 包含四个属性: character , doit , keyCode 和 stateMask 。
其中 character 我们在前面的示例中使用过,它其实就是按键对应字符,而 doit 和 ShellEvent 中的 doit 含义是相同的。
keyCode 是我们称为键码的东西,什么是键码呢?如果你打开 org.eclipse.swt.SWT 的 API 文档,你会发现里面有很多都和键盘有关的整型常量,比如 SWT.F1 , SWT.F4 , SWT.ESC , SWT.KEYPAD_3 之类,这就是他们的键码。
而 stateMask 则是用来检测 Alt , Shift , Ctrl 这些键有没有同时被按下。
用 stateMask 与这些键的键码进行位与运算,如果得到的结果不是 0 就说明这些键被按下了,比如如果 stateMake & SWT.ALT 不为零,我们就可以认为 Alt 键被按下了。
MouseEvent
MouseEvent 对应于的是鼠标事件。它同样包含四个属性: button , stateMask , x , y
button 就是说明按下的是哪个键,比如对于普通鼠标来说, 1 是左键, 2 是右键等等
stateMask 却是用来反映键盘的状态的,这和 KeyEvent 中的 stateMask 含义是相同的。
x 和 y 指的是相对于部件的横坐标和纵坐标。
你可能会觉得有点疑问,光是这么一点属性就能处理鼠标事件了么?如果我有一个滚轮鼠标,那应该用什么事件处理滚轮的动作呢?答案是:目前可能还无法利用事件模式处理,关于这一点可以参照一下这个 url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=58656
关于 EventObject 我就只介绍到这里,这当然很不够,但是我强烈建议大家在实际应用中多查阅 eclipse 和 swt 的相关文档。因为毕竟精力有限,我的目的是让大家通过这篇文章能够找到一个正确获取知识的方向,而不是把这些知识很详细的介绍给大家。
Untyped Events
我们在这里提到了 untyped events ,那肯定就有 typed event , typed 和 untyped 本身并不是说事件有什么不一样,而是说事件处理是使用了特定的 Listener 还是没有。我们前面提到的所有事件处理都是 typed 类型,因为它们都使用了特定 Listener 。
所谓的 untyped events 你可以理解为一个事件的大杂烩。和 untyped event 相联系的两个类是 Listener 和 Event 。在这里我想请大家注意一下,这两个类不是在 org.eclipse.swt.events 中,而是在 org.eclipse.swt.widgets 中。
Listener 只有一个方法 handleEvent ,这个方法里你可以处理任何事件。而如果你打开 Event 看一下,就能看到我们刚刚在前一小节中介绍过的那些 XxxEvent 中的属性在这里应有尽有。所以它可以起到替代它们的作用,当然如果是一个窗口被关闭的事件,相信你用 keyCode 属性意义不大。
让我们看一下下面一段代码
2 Listener listener = new Listener () {
3 public void handleEvent (Event e) {
4 switch (e.type) {
5 case SWT.Resize:
6 System. out .println ( " Resize received " );
7 break ;
8 case SWT.Paint:
9 System. out .println ( " Paint received " );
10 break ;
11 default :
12 System. out .println ( " Unknown event received " );
13 }
14 }
15 };
16 shell.addListener (SWT.Resize, listener);
17 shell.addListener (SWT.Paint, listener);
18
代码段 8
由此我们大体上可以体会到 untyped events 的处理方式。
小结
关于事件处理,我就向大家介绍这么多。到现在为止,我们已经基本上可以写一些简单的 swt 用户交互程序了。然而这还远远不够,毕竟人们总是希望有更华丽(或者说:丰富)的界面,让用户能够获得更好的体验。在下一节中,我计划和大家讨论一些这样的部件。
另外可能你觉得有些疑惑,为什么写了这么多内容,都是关于 swt 的呢? Jface 的内容呢?我的计划是在大部分 swt 有关的内容介绍完了以后再向大家介绍 Jface 。事实上,即使不用 Jface ,你也完全可以用 swt 构筑起一个非常完美的程序来。