让你的swt程序动起来
在向使用者提供最差的用户体验方面,中国的IT企业始终走在时代的最前端。之所以有这样的感慨其实是来源于往blog上贴上一节的内容:我用了一整天的功夫,不断与CSDN各种莫名其妙的出错提示进行斗争,最后终于成功的贴了上去。
其实作为CSDN blog一个使用者,我的要求并不高:只要能写blog,能够正常访问就可以了。然而就是这么一点基本的要求好像也得不到满足。
我不知道大家有没有这样的体验:其实软件使用者要求的东西都很基本,而现在软件做得越来越复杂,有相当大一部分是在于软件开发者把自己的注意力放在了一些附加功能(这些功能可能让用户感到惊喜,但是如果没有它们用户也不会不满意)上,而真正用户的要求却得不到满足。所以大家在设计程序的时候,一定要明白,有时候简单就是一种美,把时间花费到真正有价值的地方去。
OK,回到我们的主题上来。在这一节中,我将给大家介绍swt的事件模式。在前面我们也提到过,写一个swt程序,无非就是分几步走。其中比较需要费心的就是布置好用户界面和处理各种事件。
添加了事件处理的Hello,world!
其实swt中处理事件非常简单,对应于各种事件都有相应的listener类,如果一种事件叫做Xyz,那么对应的listener类就是XyzListener。比如对应于鼠标事件的有MouseListener,对应于键盘事件的就是KeyListener。而在每种widget中,对于它可以处理的事件都有addXyzListener方法,只要把对应的listener实例作为参数传给它就可以了。
为了更加清楚的说明,我们先来看下面一段程序:
public class EventDemo {
private Shell _shell;
public EventDemo() {
Display display = new Display();
Shell shell = new Shell(display,SWT.SHELL_TRIM);
setShell(shell);
RowLayout layout=new RowLayout();
shell.setLayout(layout);
shell.setText("Event demo");
Button button=new Button(shell,SWT.PUSH | SWT.CENTER);
button.setText("Click me!");
button.addSelectionListener(new SelectionListener(){
public void widgetSelected(SelectionEvent event) {
handleSelectionEvent();
}
public void widgetDefaultSelected(SelectionEvent event) {
}
});
shell.setBounds(200,300,100,100);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
protected void handleSelectionEvent() {
MessageBox dialog=new MessageBox(getShell(),SWT.OK|SWT.ICON_INFORMATION);
dialog.setText("Hello");
dialog.setMessage("Hello,world!");
dialog.open();
}
/**
* @param args
*/
public static void main(String[] args) {
EventDemo eventdemo=new EventDemo();
}
/**
* @return Returns the _shell.
*/
public Shell getShell() {
return _shell;
}
/**
* @param _shell The _shell to set.
*/
public void setShell(Shell shell) {
this._shell =shell;
}
}
代码段 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的话,我们的代码就可以这样写:
button.addSelectionListener(new SelectionAdapter(){
public void widgetSelected(SelectionEvent event) {
handleSelectionEvent();
}
});
这样是不是很方便呢?
EventObject:事件处理的附加信息
在处理各种事件时,我们需要一些附加信息,而EventObject给我们提供了这些信息。
我们先来看下面这个简单的小程序,在这段程序中,我们创建了两个文本框,当在第一个文本框输入时,第二个文本框中显示输入的字符。
public class EventDemo2 {
Text logText;
public EventDemo2() {
Display display = new Display();
Shell shell = new Shell(display,SWT.SHELL_TRIM);
GridLayout layout=new GridLayout();
layout.numColumns=2;
shell.setLayout(layout);
shell.setText("Event demo");
Label label1=new Label(shell,SWT.RIGHT);
label1.setText("text1:");
Text text1=new Text(shell,SWT.NONE);
text1.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e) {
Text t=getLogText();
String s=t.getText();
t.setText(String.valueOf(e.character));
}
}
);
Label label2=new Label(shell,SWT.RIGHT);
label2.setText("text2:");
Text text2=new Text(shell,SWT.NONE);
text2.setEditable(false);
text2.setBackground(new Color(display,255,255,255));
setLogText(text2);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
/**
* @param args
*/
public static void main(String[] args) {
EventDemo2 demo2=new EventDemo2();
}
/**
* @return Returns the logText.
*/
public Text getLogText() {
return logText;
}
/**
* @param logText The logText to set.
*/
public void setLogText(Text logText) {
this.logText = logText;
}
}
代码段 7
你可能没有兴趣仔细研究这么长的代码,那么让我们只关注这一小段代码:
text1.addKeyListener(new KeyAdapter(){
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键被按下了。
MouseEventMouseEvent对应于的是鼠标事件。它同样包含四个属性: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属性意义不大。
让我们看一下下面一段代码
Shell shell = new Shell ();
Listener listener = new Listener () {
public void handleEvent (Event e) {
switch (e.type) {
case SWT.Resize:
System.out.println ("Resize received");
break;
case SWT.Paint:
System.out.println ("Paint received");
break;
default:
System.out.println ("Unknown event received");
}
}
};
shell.addListener (SWT.Resize, listener);
shell.addListener (SWT.Paint, listener);
代码段 8
由此我们大体上可以体会到untyped events的处理方式。
小结关于事件处理,我就向大家介绍这么多。到现在为止,我们已经基本上可以写一些简单的swt用户交互程序了。然而这还远远不够,毕竟人们总是希望有更华丽(或者说:丰富)的界面,让用户能够获得更好的体验。在下一节中,我计划和大家讨论一些这样的部件。
另外可能你觉得有些疑惑,为什么写了这么多内容,都是关于swt的呢?Jface的内容呢?我的计划是在大部分swt有关的内容介绍完了以后再向大家介绍Jface。事实上,即使不用Jface,你也完全可以用swt构筑起一个非常完美的程序来。
最后,给自己做一个小广告,因为CSDN的blog功能实在太令人失望了,所以我希望在近期内可以把blog迁移到blogjava.net,具体地址是:http://www.blogjava.net/jayliu,欢迎大家提出宝贵意见。