讓你的
swt
程序動起來
在向使用者提供最差的用戶體驗方面,中國的
IT
企業始終走在時代的最前端。之所以有這樣的感慨其實是來源于往
blog
上貼上一節的內容:我用了一整天的功夫,不斷與
CSDN
各種莫名其妙的出錯提示進行斗爭,最后終于成功的貼了上去。
其實作為
CSDN blog
一個使用者,我的要求并不高:只要能寫
blog
,能夠正常訪問就可以了。然而就是這么一點基本的要求好像也得不到滿足。
我不知道大家有沒有這樣的體驗:其實軟件使用者要求的東西都很基本,而現在軟件做得越來越復雜,有相當大一部分是在于軟件開發者把自己的注意力放在了一些附加功能(這些功能可能讓用戶感到驚喜,但是如果沒有它們用戶也不會不滿意)上,而真正用戶的要求卻得不到滿足。所以大家在設計程序的時候,一定要明白,有時候簡單就是一種美,把時間花費到真正有價值的地方去。
OK
,回到我們的主題上來。在這一節中,我將給大家介紹
swt
的事件模式。在前面我們也提到過,寫一個
swt
程序,無非就是分幾步走。其中比較需要費心的就是布置好用戶界面和處理各種事件。
添加了事件處理的
Hello,world!
其實
swt
中處理事件非常簡單,對應于各種事件都有相應的
listener
類,如果一種事件叫做
Xyz
,那么對應的
listener
類就是
XyzListener
。比如對應于鼠標事件的有
MouseListener
,對應于鍵盤事件的就是
KeyListener
。而在每種
widget
中,對于它可以處理的事件都有
addXyzListener
方法,只要把對應的
listener
實例作為參數傳給它就可以了。
為了更加清楚的說明,我們先來看下面一段程序:
?1?public?class?EventDemo?{
?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
的話,我們的代碼就可以這樣寫:
button.addSelectionListener(
new
?SelectionAdapter(){
????????????
public
?
void
?widgetSelected(SelectionEvent?
event
)?{
????????????????handleSelectionEvent();
????????????}
????????});
這樣是不是很方便呢?
EventObject
:
事件處理的附加信息
在處理各種事件時,我們需要一些附加信息,而
EventObject
給我們提供了這些信息。
我們先來看下面這個簡單的小程序,在這段程序中,我們創建了兩個文本框,當在第一個文本框輸入時,第二個文本框中顯示輸入的字符。
?1?public?class?EventDemo2?{
?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
你可能沒有興趣仔細研究這么長的代碼,那么讓我們只關注這一小段代碼:
?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
鍵被按下了。
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
屬性意義不大。
讓我們看一下下面一段代碼
?1?Shell?shell?=?new?Shell?();
?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
構筑起一個非常完美的程序來。
最后,給自己做一個小廣告,因為
CSDN
的
blog
功能實在太令人失望了,所以我希望在近期內可以把
blog
遷移到
blogjava.net
,具體地址是:
http://m.tkk7.com/jayliu
,歡迎大家提出寶貴意見。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=374082