SWT/JFace入門指南之讓SWT程序動起來
我不知道大家有沒有這樣的體驗:其實軟件使用者要求的東西都很基本,而現在軟件做得越來越復雜,有相當大一部分是在于軟件開發者把自己的注意力放在了一些附加功能(這些功能可能讓用戶感到驚喜,但是如果沒有它們用戶也不會不滿意)上,而真正用戶的要求卻得不到滿足。所以大家在設計程序的時候,一定要明白,有時候簡單就是一種美,把時間花費到真正有價值的地方去。
OK,回到我們的主題上來。在這一節中,我將給大家介紹swt的事件模式。在前面我們也提到過,寫一個swt程序,無非就是分幾步走。其中比較需要費心的就是布置好用戶界面和處理各種事件。
添加了事件處理的Hello,world!
其實swt中處理事件非常簡單,對應于各種事件都有相應的listener類,如果一種事件叫做Xyz,那么對應的listener類就是XyzListener。比如對應于鼠標事件的有MouseListener,對應于鍵盤事件的就是KeyListener。而在每種widget中,對于它可以處理的事件都有addXyzListener方法,只要把對應的listener實例作為參數傳給它就可以了。
為了更加清楚的說明,我們先來看下面一段程序:
代碼段 6
你可以看到在這段程序中,我們只創建了一個Button,隨后調用了它的addSelectionListener()方法,在這個新創建的Listener,我們只為widgetSelected方法添加了代碼,并在其中創建了一個對話框。
如果總結一下,我們可以得出處理事件的幾個步驟:
1. 針對你所處理的事件,找出合適的XyzListener接口
2. 編寫一個新的類,這個類實現了XyzListener接口
3. 在你所感興趣的事件中編寫處理代碼,而對于那些你不感興趣的方法可以讓它們保持空白(就像實例中的widgetDefaultSelected()方法)一樣
讓事件處理更加簡單:使用適配器(adapter)
有時候我們可能會感覺這樣仍然不夠簡單,比如我只對SelectionListener中的widgetSelected()方法感興趣,但是為了能夠通過編譯器的編譯,我卻不得不寫一個空白的widgetDefaultSelected()方法(因為SelectionListener是一個接口,你必須實現它所有的方法)。
幸運的是,swt幫我們解決了這個問題,途徑就是使用adapter。在swt中,對應于一個XyzListener都有一個XyzAdapter,adapter都是抽象類并且實現了對應的listener接口,它為對應listener接口中的每個方法都定義了一個默認實現(基本上就是什么都不做),我們在使用時候只需要override掉自己感興趣的方法就可以了。
結合上一小節中的代碼,如果使用SelectionAdapter代替SelectionListener的話,我們的代碼就可以這樣寫:
這樣是不是很方便呢?
EventObject:事件處理的附加信息
在處理各種事件時,我們需要一些附加信息,而EventObject給我們提供了這些信息。
我們先來看下面這個簡單的小程序,在這段程序中,我們創建了兩個文本框,當在第一個文本框輸入時,第二個文本框中顯示輸入的字符。
代碼段 7
你可能沒有興趣仔細研究這么長的代碼,那么讓我們只關注這一小段代碼:
在這段代碼中,我們使用了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屬性意義不大。
讓我們看一下下面一段代碼
代碼段 8
由此我們大體上可以體會到untyped events的處理方式。
小結
關于事件處理,我就向大家介紹這么多。到現在為止,我們已經基本上可以寫一些簡單的swt用戶交互程序了。然而這還遠遠不夠,畢竟人們總是希望有更華麗(或者說:豐富)的界面,讓用戶能夠獲得更好的體驗。在下一節中,我計劃和大家討論一些這樣的部件。
另外可能你覺得有些疑惑,為什么寫了這么多內容,都是關于swt的呢?Jface的內容呢?我的計劃是在大部分swt有關的內容介紹完了以后再向大家介紹Jface。事實上,即使不用Jface,你也完全可以用swt構筑起一個非常完美的程序來。
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方法添加了代碼,并在其中創建了一個對話框。
如果總結一下,我們可以得出處理事件的幾個步驟:
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構筑起一個非常完美的程序來。