作者:Michael Turner
介紹
Erlang這門程式語言通常用於伺服器方面,雖然它也有類似Wings 3D這樣影象密集的應用。wxWidget是對Erlang支援最好的影象API,它為GUI程式設計提供一個大型,成熟,穩定的跨平臺API。
1。 怎樣閱讀該教程,為什麼要讀它
這個教程的教學方法是高度互動的:首先你將在Erlang shell中進行wxWidget相關呼叫。然後你可以立刻看到它的效果。當你新增一個東西后它能立即呈現。 這種方法是學習GUI程式設計更好的方式,原因如下:
更短更簡潔:大多數GUI教程依賴於一個GUI demo程式,但問題是即使最簡單的GUI demo程式碼也有一頁多。並且它們不僅僅是長,還寬和多:GUI呼叫通常有一大堆的引數。很難同時關注一兩行。Erlang shell幫助你解決這些問題,而且當你犯錯的時候很容易知道什麼做錯了。
快速反饋:當你瀏覽GUI程式碼時,你通常想知道:它到底發生了什麼,什麼時候發生的?在命令列中,你就能知道。或者說至少你能快速找出。
更少恐懼(或者說更少無聊):看看GUI程式設計中任何一個“hello world”程式,無論什麼語言都可以。如果你早就知道GUI APIs,你就會抱怨:有好多無關緊要的不同要學啊!如果你不知道,你就會顫抖:有好多要學啊!
1。1 讓手指動起來
你想透過這個教程獲得最大收益嗎?你是把這個教程直接複製到你執行的Erlang上的嗎?那麼你得記住一個最重要的事情:不要複製貼上到你的Erlang shell上。你想嘗試記住別人說的話然後複述一次這樣來練習口語嗎?教程說在哪寫程式碼,就在哪寫程式碼,
2。1 反對意見?駁回!
當然這種方法也有缺點。沒有任何事物是完美的。類似這樣的反對意見:
“純手打意味著犯一堆錯誤”——當然,那是好事。錯誤幫助你學習。
“要輸入很多內容”——可能你擔心手腕患病?考慮:壓力也是犯病因子。學一門新程式設計技術即使很有趣也可能會有壓力。這只是降低壓力的學習方法的一種。而且:你將在每個l小節結束後有停頓時間。
“浪費很多時間”——注意,你學到的GUI APIs一個也不會少。
說了這麼多為什麼不現在開始呢?
2。 準備工作
不像專門為特定系統設計的快速GUI原型程式,如Tcl/Tk,在從Erlang shell命令列開始圖形介面開發之前有一些準備工作要做。本節逐步引導你。在這個部分,假設你早已:
安裝了Erlang
已經知道怎麼使用Erlang shell
有用過合適的文字編輯器寫程式
在開始之前,將你的Erlang shell準備好會更容易理解。
2。1 尋找wx標頭檔案
眼見為實——但是前提是你得看清楚。並非所有wx呼叫都產生一個直觀的圖形顯示;在Erlang shell中,通常你只能看到返回值。這些值可能是神秘的,尤其是如果你過去沒有用過Erlang。因為Erlang是一門函數語言程式設計語言,所以每個wx呼叫都會返回一個值。這些值大多是tuple。而這些tuple又大多有記錄(record)的內容。在記錄格式下理解wx返回值會更容易。Erlang shell需要被告知wx的定義。那麼現在的問題是,這些記錄在哪定義的? 尋找在你係統上wx定義在哪,可以輸入這個:
My_wx_dir = code:lib_dir(wx)。
在Windows XP上,My_wx_dir可能看起來像這樣:
"c:/PROGRA~1/ERL57~1.2/lib/wx-0.98.2"
2。2 閱讀wx模組定
從剛剛獲取的那個目錄讀取wx定義:
rr
(
My_wx_dir
++
“/include/wx。hrl”
)。
rr
(
My_wx_dir
++
“/src/wxe。hrl”
)。
兩個rr呼叫都應該返回一系列模組,如果rr調用出錯,你將得到空列表。 開啟你的文字編輯器。把上面三行程式碼複製貼上到一個臨時檔案。然後,當你開始一個新小節,或者重新開始小節,或者因為崩潰不得不重啟,你可以把它們複製貼上到shell裡。現在就試試,確保它們能正常工作。退出Erlang,重啟,然後複製貼上這些程式碼到shell。
很不幸,我們不能在shell中使用Erlang宏定義。不過這也是另一個需要定位
wx.hrl
檔案的理由:為了在shell中使用,我們得查詢需要的wx宏符號所對應的值。
2。3 初始化wx
要想啟動wx,先輸入:
Wx
=
wx
:
new
()。
它運行了嗎?可能有訊息說SMP(對稱多處理)沒有開啟,或者“SMP emulator required”。在一些Windows Erlang發行版中SMP沒有預設開啟。退出Erlang shell然後帶參
-smp
重啟,在DOS命令列中就像這樣:
"C:\Program Files\erl5.8.1.1\bin\werl.exe" -smp
如果wx:new()正常執行,將會返回一個記錄,類似下面:
#wx_ref
{
ref
=
0
,
type
=
wx
,
state
=
[]}
現在忽略它的值。
3。 Frame:開胃菜
在wxWidgets中,一個視窗相當於一個frame。讓我們寫一個簡單的程式,然後新增它。
3。1 生成一個frame
輸入下面的程式碼可以生成一個frame:
F=wxFrame:new(Wx, -1, “Hello, World!”)。
(第二個引數可以是一個整數ID,-1告訴wxWidgets給frame一個預設值) 現在,新frame在哪?我們得到一個類似這樣的記錄:
#wx_ref{ref = 35,type = wxFrame,state = []}
但是螢幕上沒有任何改變。為什麼?我們必須提出想看看frame的請求它才會出現。輸入:
wxFrame
:
show
(
F
)。
它會返回true。現在你就會看到類似這樣的東西:
3。2 從shell異常中恢復
只需要點選關閉按鈕就能關閉frame。但是別那麼做,先試試下面這個無意義的呼叫;
nothing
:
doint
()。
這會讓frame消失,隨之出現的還有異常錯誤訊息。這是因為wxWidgets在它(shell)的程序執行圖形程式,如果shell中出現異常又不捕獲,它就會當即被殺死。
僅僅鍵盤輸入錯誤就有可能導致GUI完全丟失,這種情況而且會經常發生。無論在哪隻要你引發了錯誤就要重新輸入一遍。沒人願意來上幾次吧?所以,在開始教程之前,輸入下面的程式碼:
catch_exception
(
true
)。
現在休息一下吧!這樣設定後可以讓GUI程式無視引發錯誤的地方繼續工作。把上面的程式碼全都放到一個臨時檔案,像這樣:
catch_exception
(
true
)。
My_wx_dir
=
code
:
lib_dir
(
wx
)。
rr
(
My_wx_dir
++
“/include/wx。hrl”
)。
rr
(
My_wx_dir
++
“/src/wxe。hrl”
)。
Wx
=
wx
:
new
()。
F
=
wxFrame
:
new
(
Wx
,
-
1
,
“Hello, World!”
)。
wxFrame
:
show
(
F
)。
3。3 Frames: 有創意的銷燬
當frame在螢幕上現實時,輸入:
wxFrame
:
destory
(
F
)。
它應該返回ok然後frame消失了。現在,就當是開心一下,建立多個frame:
建立一個標題為“Hey!”的wxFrame,變數名為F1
顯示F1
建立一個標題為“Boo!”的wxFrame,變數名為F2
顯示F2。
使用wxFrame:destroy將兩個frame銷燬。 別把這些程式碼放到臨時檔案,我們的lesson要從第一個destroy呼叫繼續。
4。狀態列
狀態列不僅方便程式功能,也便於除錯。
4。1 生成一個狀態列
輸入:
wxFrame
:
createStatusBar
(
F
)。
現在你的frame就會像這樣:
4。2 設定狀態列文字
將一些文字放到狀態列中:
wxFrame
:
setStatusText
(
F
,
“Quiet here。”
)。
現在看起來像這樣:
花一點時間把上面這些程式碼複製貼上到你的臨時檔案。嘗試向狀態列設定其他文字,然後恢復為“Quiet here”:
SB
=
wxFrame
:
getStatusBar
(
F
)。
wxStatusBar
:
pushStatusText
(
SB
,
“A LITTLE LOUDER NOW。”
)。
wxStatusBar
:
popStatusText
(
SB
)。
現在應該已經回到了之前你向狀態列新增文字的樣子。
5。 選單欄
按照慣例wxWidgets中的frame都會偶一個選單欄。這樣看起來狀態列選單欄沒什麼區別。然而,選單欄通常由其他東西組成:它們需要被組合到一起。
在wxWidgets中,複雜的東西通常都是由簡單的東西開始一步步構建的。wxWidgets的API不假設新建的複雜的東西包含任何簡單的東西。對於越複雜的東西,所需的構建步驟就越多。
讓我們儘快生成一個可見的選單欄。當你完成後記得複製下面的程式碼到你的臨時檔案,
5。1 生成一個選單欄
輸入:
MenuBar
=
wxMenuBar
:
new
()。
你將會看到shell輸出:
#wx_ref{ref = 37,type = wxMenuBar,state = []}
但是frame仍然沒有選單欄吧?視窗上沒有任何改變。這是什麼問題導致的呢?
我們有看到選單欄關聯frame嗎?你知道它在哪(譯註:就是指MenuBar),但是wx不能讀取你的腦子。是的,F是到目前為止你僅有的frame。但是wx不假設你想把MenuBar放到F裡面去。
5。1 關聯選單欄與Frame
嘗試一下將MenuBar設定為F的一部分:
wxFrame
:
setMenuBar
(
F
,
MenuBar
)。
它可能會返回ok,但是。。。視窗還是沒有任何東西!到底發生了什麼?有一個方法可以問問frame的選單欄。輸入:
wxFrame
:
getMenuBar
(
F
)。
你在shell看到它返回了什麼?和之前setMenuBar返回了一樣的wx_ref。的確發生了選單欄和frame的關聯。問題是:frame沒有顯示這個已經關聯的選單欄。我們得做點什麼。
6。 選單
下面幾步將新增選單項到選單欄,然後顯示它。
6。1 生成選單
大多數GUI應用程式都有一個File(檔案)選單。輸入這個:
FileMn
=
wxMenu
:
new
()。
你會看到:
#wx_ref{ref = 37,type = wxMenu,state = []}
又是這樣,wxWidgets不知道你想把這個選單新增到哪所以不會顯示。你必須告訴FileMn它應該被放到哪個選單欄。現在我們把FileMn放到F的選單欄MenuBar裡:
wxMenuBar
:
append
(
MenuBar
,
FileMn
,
“&File”
)。
(“File”前面的“&”符號表示你可以輸入快捷鍵Alt-F使用它)
你會看到視窗有一些改變,就像這樣:
但是一個好的選單沒有選單項怎麼行。點選File選單(或者Alt-F) 好吧,沒有任何東西,那是怎麼回事?
6。2 新增一個選單項
每個File選單都有一個Quit選單項,讓我們也新增一個,輸入:
Quit
=
wxMenuItem
:
new
([{
id
,
400
},{
text
,
“&Quit”
}])。
還是之前那樣,你不會看到任何圖形上的改變,只在shell裡面有一個返回值。你需要把它新增到一個選單裡面:
wxMenu
:
append
(
FileMn
,
Quit
)。
點選File選單,現在可以看到:
回顧上述設定的選單的所有程式碼,你會看到:
MenuBar
=
wxMenuBar
:
new
()。
wxFrame
:
setMenuBar
(
F
,
MenuBar
)。
FileMn
=
wxMenu
:
new
()。
wxMenuBar
:
append
(
MenuBar
,
FileMn
,
“&File”
)。
Quit
=
wxMenuItem
:
new
([{
id
,
400
},{
text
,
“&Quit”
}])。
wxMenu
:
append
(
FileMn
,
Quit
)。
我們還可以新增什麼呢?每一個得體的應用程式都有一個Help選單。然後Help選單通常有一個About選單項。
6。3 新增一個Help選單
重複你之前新增File選單所用的
new append
命令:
HelpMn
=
wxMenu
:
new
()。
wxMenuBar
:
append
(
MenuBar
,
HelpMn
,
“&Help”
)。
你可以看到這個:
現在新增一個About選單項。同樣重複之前將Quit選單項新增到File選單的步驟。你會輸入這樣的程式碼:
About
=
wxMenuItem
:
new
([{
id
,
500
},{
text
,
“About”
}])。
wxMenu
:
append
(
HelpMn
,
About
)。
點一下Help選單,你會看到這個:
花一點時間把程式碼複製貼上到你的臨時檔案。
7。事件
到目前為止,我們所做的都沒有涉及事件。你可能認為Erlang wxWidgets沒有事件。如果你現在輸入
flush().
,你就不會那樣想了。 事實上,在wxWidgets中每個滑鼠點選都會觸發事件。它們被wx以預設的一些方式處理。通常,wx的預設處理方式是忽略它們。讓我們捕獲事件,看看它到底是什麼樣的。
7。1 使用connect檢視事件
輸入:
wxFrame
:
connect
(
F
,
close_window
)。
點選frame上的關閉按鈕,然後輸入:
flush
()。
你會看到這樣的輸出:
Shell
got
{
wx
,
-
202
,{
wx_ref
,
35
,
wxFrame
,[]},[],{
wxClose
,
close_window
}}
注意,現在點選關閉按鈕不會真正的關閉一個frame。你將重寫這個預設行為。 多點幾次關閉按鈕,然後點選最大化視窗按鈕,最小化視窗按鈕。然後再次輸入
flush().
。你會看到
close_window
事件,但是沒有最大化最小化事件。
同樣請注意shell怎樣輸出它收到的事件:它不會使用之前讀取的wx定義。你只會看到原始tuple。這使得我們知曉這些wx事件是什麼的難度增加。
有一個使用記錄定義檢視事件的方法。下面的
fun
返回一個事件。輸入:
Ev
=
fun
()
->
receive
E
->
E
after
0
->
empty
end
end
。
點選關閉按鈕,然後呼叫事件讀取器:
Ev
()。
你會看到類似這樣的東西:
#wx
{
id
=
-
202
,
obj
=
#wx_ref
{
ref
=
35
,
type
=
wxFrame
,
state
=
[]},
userData
=
[],
event
=
#wxClose
{
type
=
close_window
}}
7。2 選單選擇事件
讓我們嘗試關聯(connect)更多事件。輸入:
wxFrame
:
connect
(
F
,
command_menu_selected
)。
嘗試選擇選單。選擇
File->Quit
,然後選擇
File->About
。然後輸入
Ev().
看看生成了哪些事件。除了id外,返回的事件應該都是一樣的。
#wx
{
id
=
400
,
obj
=
#wx_ref
{
ref
=
35
,
type
=
wxFrame
,
state
=
[]},
userData
=
[],
event
=
#wxCommand
{
type
=
command_menu_selected
,
cmdString
=
[],
commandInt
=
0
,
extraLong
=
0
}}
7。3 回撥
知道發生了什麼事件很有用。有時它有助於觀察細節。但是大多數時候,我們只想當事件發生時做出我們希望的動作。所以我們必須捕獲事件,然後搞懂怎樣給它新增一個動作。
wx中的事件由回撥函式處理。首先,生成一個回撥函式。輸入:
Ding
=
fun
(_,_)
->
wx_misc
:
bell
()
end
。
試試,給它傳入正確的引數。
Ding
(
#wx
{},
#wx_ref
{})。
它會響鈴嗎?會的。
現在將它關聯到你的frame的close_windows事件上:
wxFrame
:
connect
(
F
,
close_window
,
[{
callback
,
Ding
}])。
再試試點選關閉按鈕。就會有嗶嗶聲。試試呼叫
Ev().
它不再返回close_window事件。
因為簡單的嗶嗶聲對於這個關閉視窗事件是沒有實際意義的,你可能想解除關聯
wxFrame
:
disconnect
(
F
,
close_window
)。
8。對話方塊
一個“About”選單項應該給我們顯示一個模態對話方塊。但是怎樣生成這個對話方塊?這裡是最簡單的方法。
8。1 生成一個模態對話方塊
輸入下面這行程式碼:
D
=
wxMessageDialog
:
new
(
F
,
“Let‘s talk。”
)。
它應該返回一個類似這樣的指:
#wx_ref
{
ref
=
43
,
type
=
wxMessageDialog
,
state
=
[]}
但是螢幕上不會有任何顯示。
8。2 顯示模態對話方塊
要想顯示對話方塊並與之互動,輸入:
wxMessageDialog
:
showModal
(
D
)。
在你的螢幕上就會看到類似這個:
因為對話方塊是模態的,所以直到你點OK之前shell都不會有任何返回值。返回值應該是5100。如果你看看
wx.hrl
,你就會知道它代表wxID_OK。
全文完
(但我怎麼感覺還有後續內容。。。可能是我沒找全,如果後續內容找到了我會更新:) )