Panda3D提供一些基本2d gui模組,稱做Direct GUI,參考這裡。
Login 畫面(LoginFrame.py)是由DirectLabel、DirectEntry、DirectRadioButton、 DirectButton等元件組成,如下圖所示。在需要取得文字輸入之處使用DirectEntry,如輸入使用者暱稱以及輸入server ip 位址。需要多選一之處使用DirectRadioButton,如選擇開啟聊天室或加入。需要取得按鈕動作之處使用DirectButton,如按下 Login按鈕表示以這些輸入資料進行連線動作。而DirectLabel用在表示提示文字。其他gui介紹請參考panda3d網頁。
ChatRoom 畫面(ChatRoomFrame.py)是由DirectEntry、DirectButton與自定義的ScrolledTextFrame組成,如 下圖所示。按鈕Host IP按下後會跳出個對話框,用以顯示server端ip位址。聊天室對話的文字輸入以DirectEntry處理。
顯 示聊天室文字與顯示目前聊天室成員清單,需要可捲動的文字顯示gui,雖然可以由DirectScrolledList內嵌DirectLabel實作, 但處理捲動的部份個人覺得不夠好,因此嘗試以DirectScrolledFrame內嵌OnscreenText來實作。 DirectScrolledFrame的捲動部份比較類似一般在視窗上看到的捲動棒(scrolled bar),而OnscreenText也提供appendText函數用來新增文字。美中不足的,DirectScrolledFrame並不會自動調整 捲動機制以符合內嵌物件大小,因此必須在每次OnscreenText因為新增文字而使大小改變時,重新設定DirectScrolledFrame捲動 大小(ScrolledTextFrame.py,第68行。 ScrolledTextFrame.resetCanvasSizeByTextBoard函數)。其中,mySetCanvasSize函數指向 ScrolledTextFrame.guiItem.setVirtualFrame函數,setVirtualFrame可用來設定 DirectScrolledFrame的內嵌物件大小,以改變捲動棒行為。
Direct GUI功能有限,行為比較複雜的gui元件可嘗試用基本元件組合,例如此練習中的可捲動的文字顯示組合自DirectScrolledFrame與 OnscreenText。另外,有些函數並不直接提供,藏在gui 內部物件的函數中,如DirectScrolledFrame 中用以改變內嵌物件大小的函數setVirtualFrame 藏在其內部物件guiItem 中。因此,翻翻原始碼(py檔)也許可以找到些gui 元件不直接提供的功能。
再者,Direct GUI對於gui元件擺設位置,大小的設定不是很直覺,它是以視窗大小的寬當成1,如像素800x600視窗,大小是(1.333, 1),因此像素300x300的gui 物件大小是(0.5, 0.5)。某些物件的擺設位置(0,0,0) 表示視窗正中央,而某些物件擺設位置的(0,0,0) 卻是相對另外一個物件的相對位置。
除了Direct GUI,也有其他使用者貢獻的gui模組,如TreeGUI,甚至還提供可拖曳的視窗。
----
2010年2月17日 星期三
聊天室練習 - VII. 中文輸入與顯示
在聊天室練習中,尚未找到支援中文輸入的方法,僅完成中文顯示。
要顯示中文,必須在Panda3D執行環境裡指定含有中文字型的字集。某些 Direct GUI,如OnscreenText,可設定gui 元件個別的字集。或是在Panda3D設定檔中指定預設字集(UnicodeDefine.py,第15行),如此所有字型相關的顯示都使用此預設字集。
在python原始檔中嵌入中文字,得做些編碼設定,見ChatRoomMain.py,第一行。
----
要顯示中文,必須在Panda3D執行環境裡指定含有中文字型的字集。某些 Direct GUI,如OnscreenText,可設定gui 元件個別的字集。或是在Panda3D設定檔中指定預設字集(UnicodeDefine.py,第15行),如此所有字型相關的顯示都使用此預設字集。
在python原始檔中嵌入中文字,得做些編碼設定,見ChatRoomMain.py,第一行。
# -*- coding: utf-8 -*-設定此檔案以utf-8方式編碼。
----
聊天室練習 - VIII. 事件驅動
事件驅動的優缺點於先前的文章已經描述過,在此就不再重複。僅以一個簡單的測試來檢驗啟動事件與事件執行的順序。
在ChatRoomMain.py,第130行之後插入
當輸入對談訊息後,ChatRoomMain.CommandParser啟動事件event_TEXTDISPLAY,對應的事件執行在ChatRoomFrame.CmdTextDisplayHandle。列印出的訊息如下:
從列印出的訊息可以看出,每次啟動事件(ChatRoomMain.py,第130行)後,總是先處理事件執行(ChatRoomFrame.py,第81 行),執行完後才再回到啟動事件處繼續執行(ChatRoomMain.py,第131行,print 'return to CommandParser')。猜測啟動事件後的事件執行並不會獨立出一個thread來處理,而是如同函數呼叫般直接跳至call back function。因此ChatRoomMain.py,第130行
在聊天室練習中,由於環境較單純,使用事件驅動似乎沒得到明顯的好處。
假設一個遊戲過程中,不同階段都有不同的處理對談文字顯示的函數。在不使用事件驅動的方式下,呼叫端(例如此練習中的CommandParser)必須要以階段來判斷該執行哪個文字顯示函數,並且當文字顯示函數更動名稱時,例如改變函數名稱或是換到不同類別內,呼叫端也要跟著更新。
但若使用事件驅動的方式,呼叫文字顯示與執行文字顯示的函數間以一個事件名稱(例如此練習中的event_TEXTDISPLAY)當介面連接,只要執行文字顯示的函數綁到此事件名稱的call back function,就可以在不更動呼叫端程式碼的情形下改變執行文字顯示的函數。因此,使用事件驅動應該可以減少程式維護上的成本。
為了避免事件名稱的衝突,在此練習中統一將事件名稱定義在CommandInputLib.py的最後。
----
在ChatRoomMain.py,第130行之後插入
print 'return to CommandParser'另外,在ChatRoomFrame.py,第81行之後插入
print 'enter event_TEXTDISPLAY handle'
當輸入對談訊息後,ChatRoomMain.CommandParser啟動事件event_TEXTDISPLAY,對應的事件執行在ChatRoomFrame.CmdTextDisplayHandle。列印出的訊息如下:
enter event_TEXTDISPLAY handle
return to CommandParser
enter event_TEXTDISPLAY handle
return to CommandParser
enter event_TEXTDISPLAY handle
return to CommandParser
enter event_TEXTDISPLAY handle
return to CommandParser
enter event_TEXTDISPLAY handle
return to CommandParser
從列印出的訊息可以看出,每次啟動事件(ChatRoomMain.py,第130行)後,總是先處理事件執行(ChatRoomFrame.py,第81 行),執行完後才再回到啟動事件處繼續執行(ChatRoomMain.py,第131行,print 'return to CommandParser')。猜測啟動事件後的事件執行並不會獨立出一個thread來處理,而是如同函數呼叫般直接跳至call back function。因此ChatRoomMain.py,第130行
messenger.send(event_TEXTDISPLAY,[cmdItem['i'], cmdItem['param']])可以看成
self.ChatRoomFrame.CmdTextDisplayHandle(cmdItem['i'], cmdItem['param'])
在聊天室練習中,由於環境較單純,使用事件驅動似乎沒得到明顯的好處。
假設一個遊戲過程中,不同階段都有不同的處理對談文字顯示的函數。在不使用事件驅動的方式下,呼叫端(例如此練習中的CommandParser)必須要以階段來判斷該執行哪個文字顯示函數,並且當文字顯示函數更動名稱時,例如改變函數名稱或是換到不同類別內,呼叫端也要跟著更新。
但若使用事件驅動的方式,呼叫文字顯示與執行文字顯示的函數間以一個事件名稱(例如此練習中的event_TEXTDISPLAY)當介面連接,只要執行文字顯示的函數綁到此事件名稱的call back function,就可以在不更動呼叫端程式碼的情形下改變執行文字顯示的函數。因此,使用事件驅動應該可以減少程式維護上的成本。
為了避免事件名稱的衝突,在此練習中統一將事件名稱定義在CommandInputLib.py的最後。
----
聊天室練習 - III. 網路設定、接收與傳送
Panda3D的網路設定參考這裡。
NetworkLib.py 中,函數NetworkSetupServer與NetworkSetupClient分別設定連線為server或是client端。連線成功後,將處理網路封包讀取與檢查連線狀態的task加至系統中,server端還會多加入"聽"client連結的task。
NetworkConnectionTarget 這個list保存連線的對象。網路封包讀取時,用以標明是哪個對象送來的封包(NetworkLib.py,第229行)。與網路封包傳送時,用以表示送 給哪個對象的封包(NetworkLib.py,第175行,第189行)。檢查連線狀態與移除連線時也用於表示對象(NetworkLib.py,第 248行,第256,257行)。server端的NetworkConnectionTarget依序存放連進來的client。client端的 NetworkConnectionTarget僅保存server為其唯一的連線對象。
網路封包傳送的運作,只要 呼叫NetworkDataObjSend或NetworkDataObjSendAll函數。在這個練習中,NetworkDataObjSend是設 計給client,以傳送給它唯一的對象,NetworkDataObjSendAll是設計給server,以傳送封包給它所有的client對象。設計的原因在稍後討論Server - Client 間的傳輸協定時在說明。
雖然panda3D支援將整數,浮點數,字串等形態物件以個別方式填入封包,參考這裡, 如整數就依byte需求分成getInt8,getInt16,getInt32,getInt64等處理函數。針對不同需求使用對應函數應該會得到較小的封包,但讀取時需要依序呼叫對應的處理函數,相當於寫一個parser來還原封包內容,當傳輸內容或順序更改時,對應的還原封包函數也得變動。且這些支 援的型態並不包含list或dictionary等python內建型態。使用此方式包裝資料有取得較小封包的優點,但代價是較複雜的還原封包內容的方式。
這個練習中並沒有使用上述方式,而是以python提供的cPickle.dumps函數,將python物件打包成字串,再將此字串以zlib.compress壓縮後傳送。還原封包的流程正好相反,將讀取的封包內容字串以zlib.decompress解壓 縮,再用cPickle.loads還原成物件。這樣雖然會產生較大的封包,但在處理資料填入封包與還原的方式較簡單,並且可以傳送任何python物件,於新一版panda3D 1.7中,甚至可以傳送panda3d內的自訂物件如point3等。
下圖為資料由接收端,經過網路至傳送端,傳送端解開資料並處理的示意流程。
----
NetworkLib.py 中,函數NetworkSetupServer與NetworkSetupClient分別設定連線為server或是client端。連線成功後,將處理網路封包讀取與檢查連線狀態的task加至系統中,server端還會多加入"聽"client連結的task。
NetworkConnectionTarget 這個list保存連線的對象。網路封包讀取時,用以標明是哪個對象送來的封包(NetworkLib.py,第229行)。與網路封包傳送時,用以表示送 給哪個對象的封包(NetworkLib.py,第175行,第189行)。檢查連線狀態與移除連線時也用於表示對象(NetworkLib.py,第 248行,第256,257行)。server端的NetworkConnectionTarget依序存放連進來的client。client端的 NetworkConnectionTarget僅保存server為其唯一的連線對象。
網路封包傳送的運作,只要 呼叫NetworkDataObjSend或NetworkDataObjSendAll函數。在這個練習中,NetworkDataObjSend是設 計給client,以傳送給它唯一的對象,NetworkDataObjSendAll是設計給server,以傳送封包給它所有的client對象。設計的原因在稍後討論Server - Client 間的傳輸協定時在說明。
雖然panda3D支援將整數,浮點數,字串等形態物件以個別方式填入封包,參考這裡, 如整數就依byte需求分成getInt8,getInt16,getInt32,getInt64等處理函數。針對不同需求使用對應函數應該會得到較小的封包,但讀取時需要依序呼叫對應的處理函數,相當於寫一個parser來還原封包內容,當傳輸內容或順序更改時,對應的還原封包函數也得變動。且這些支 援的型態並不包含list或dictionary等python內建型態。使用此方式包裝資料有取得較小封包的優點,但代價是較複雜的還原封包內容的方式。
這個練習中並沒有使用上述方式,而是以python提供的cPickle.dumps函數,將python物件打包成字串,再將此字串以zlib.compress壓縮後傳送。還原封包的流程正好相反,將讀取的封包內容字串以zlib.decompress解壓 縮,再用cPickle.loads還原成物件。這樣雖然會產生較大的封包,但在處理資料填入封包與還原的方式較簡單,並且可以傳送任何python物件,於新一版panda3D 1.7中,甚至可以傳送panda3d內的自訂物件如point3等。
網 路封包讀取的操作為:每個frame執行tskReaderPolling這個task,檢查是否有封包進入。當封包進入時,以前一段的描述方式從封包擷取資料,最後呼叫封包處理函數NetworkDataObjGet。NetworkDataObjGet為一空函數,預留給真正做資料處理的函數覆載 (ChatRoomMain.py,第40行)
下圖為資料由接收端,經過網路至傳送端,傳送端解開資料並處理的示意流程。
----
聊天室練習 - IV. Server - Client 間的傳輸協定
server 端與client 端間的傳輸協定的目標為執行命令的同步,在此的"執行命令的同步"是指所有參予聊天室的成員,其對話文字顯示順序相同。實作方式為,server端蒐集所有的執行命令,再將這些命令傳送給每個client,如此server 與每個client 都有相同的執行命令。因此client要做的是,把自己的執行命令傳給server。server 端與client 端間的傳輸如下圖所示。
雖然上圖的流程達成了每個參予者的執行命令同步,但還不夠完全。從命令輸入到命令執行這段延遲時間(上圖的delayT),對於每個參予者而言不保證相等。在這個練習中,程式於delayT之間不做任何事情,因此看不出不等長的delayT造成的影響。但如果在即時性的環境中,每一個frame都有可能使狀態改變,假設一個遊戲中的每個frame,參予者都會不斷向前移動,而命令輸入用以改變參予者的移動方向,若每個參與者的delayT不等長,則每個參予者將不會得到完全一樣的執行結果。
因此在需要即時性的環境中,必須要保證執行命令不只在順序上同步,也要在時間上同步。實作方式也許可以讓server 無論有無收到參予者的執行命令,都要按時傳送執行命令串給client 端,client 端要在協定的時間內取得server端傳來的執行命令,如果沒收到server端的命令串則凍結即時環境的運行直到收到命令串。如此所有參與者能在相同delayT時,執行命令串。接著client 再傳個"命令已經正確被執行"的回覆給server,server 收到所有client的回覆後,才算完成這一輪的傳輸,同樣的,如果沒收到所有 client的回覆,就得凍結即時環境的運行直到收到所有回覆。在server 的即時環境的運行被凍結時,client 端會因為收不到server 傳來的下一輪命令串而同樣得凍結即時環境的運行。為了避免無止盡的等待,在等待時加入timeout機制來限制等待時間。
以上描述可以下圖表示,每一個參予者的每一次執行命令迴圈由三部份組成:
上圖只是初步的構想,也許有其他更簡潔的傳輸協定方式來達成在即時性環境中的執行命令同步。
在更單純如回合制的環境下,server-client 的傳輸機制將會更簡單。這部份的討論留待將來再進行。
----
因此在需要即時性的環境中,必須要保證執行命令不只在順序上同步,也要在時間上同步。實作方式也許可以讓server 無論有無收到參予者的執行命令,都要按時傳送執行命令串給client 端,client 端要在協定的時間內取得server端傳來的執行命令,如果沒收到server端的命令串則凍結即時環境的運行直到收到命令串。如此所有參與者能在相同delayT時,執行命令串。接著client 再傳個"命令已經正確被執行"的回覆給server,server 收到所有client的回覆後,才算完成這一輪的傳輸,同樣的,如果沒收到所有 client的回覆,就得凍結即時環境的運行直到收到所有回覆。在server 的即時環境的運行被凍結時,client 端會因為收不到server 傳來的下一輪命令串而同樣得凍結即時環境的運行。為了避免無止盡的等待,在等待時加入timeout機制來限制等待時間。
以上描述可以下圖表示,每一個參予者的每一次執行命令迴圈由三部份組成:
- 等待m 個frame。client 以m 個frame的緩衝時間接收命令串。
- 執行命令串。
- 等待n 個frame。server 以n 個frame的緩衝時間接收所有client 的回覆(reply)。
如果超過等待時間,沒取得該收到的資料,則會進入凍結環境的狀態(frozen game world)。如果再過一段時間還是沒收到資料(timeout),則進入處理斷線的程序。
上圖只是初步的構想,也許有其他更簡潔的傳輸協定方式來達成在即時性環境中的執行命令同步。
在更單純如回合制的環境下,server-client 的傳輸機制將會更簡單。這部份的討論留待將來再進行。
----
聊天室練習 - V. 傳輸命令格式
在聊天室這個練習中,網路接收與傳送是以單一物件為主,見"III、 網路設定、接收與傳送"。在這個架構下,每個命令以一個python內建型態--字典(dictionary )來包裝,內含三個欄位:
欄位"i"又分成兩種。
欄位"fn"總是字串,定義在CommandInputLib.py中。欄位"param"隨著"fn"而有所不同格式。
此練習中使用下列"fn":
傳輸命令格式的設計目的是希望藉由"i"來分辨參與者,將"fn"與"param"傳遞給對應的參予者處理。如此參予者的行為可交由不同人獨立撰寫,自行定義私人的傳輸命令,僅以一個共同介面如
----
- "i":傳送命令者的索引代號
- "fn":命令名稱
- "param":命令參數
欄位"i"又分成兩種。
- i=0:系統命令,處理特殊命令如參予者加入命令,參予者離開等事件。
- i != 0:參予者輸入的命令。如聊天字串輸入,鍵盤按下,搖桿輸入等。
欄位"fn"總是字串,定義在CommandInputLib.py中。欄位"param"隨著"fn"而有所不同格式。
此練習中使用下列"fn":
- cmd_CLIENTINITIAL:client加入時的初始化處理。在此server將傳遞聊天室內的參予者暱稱清單,但只回覆給傳送此命令的client,而不是給所有的參予者。
- cmd_USERJOIN:其他client加入時的處理。將加入者的暱稱加入參予者暱稱清單。
- cmd_USERLEAV:其他client離開時的處理。將離開者的暱稱從參予者暱稱清單移除。
- cmd_TEXT:參予者的對話字串處理。將此字串與發話者暱稱連結("發話者暱稱:字串"),加入文字顯示區。
傳輸命令格式的設計目的是希望藉由"i"來分辨參與者,將"fn"與"param"傳遞給對應的參予者處理。如此參予者的行為可交由不同人獨立撰寫,自行定義私人的傳輸命令,僅以一個共同介面如
CmdExec(fn,param)來接收命令。
----
聊天室練習 - I. 操作方式
開啟:
執行 python -E ChatRoom.py
操作:
----
執行 python -E ChatRoom.py
操作:
- login:輸入Nickname,選擇"Create a Chat Room"(建立聊天室)或是"Join to"(加入)。加入的一方(client)輸入建立的一方(server)的ip 在其後。按下"Login"按鈕以建立連線,成功建立連線後進入步驟2。
- chat room: 下方輸入文字(目前只處理英數)後按enter送出文字。"Host IP"按鈕可以顯示server 的ip 位址。
----
聊天室練習 - II. 程式架構
起初的構想是以一個對話框來取得暱稱及連線方式。如果用wxPython來做,應該會是個blockiing 的對話框。但在panda3D並沒有找到現有的對話框函數,因此將操作流程設計成有限狀態機,其行為如下圖描述。
Panda3D的FSM介紹參考這裡。
當程式啟動時,進入Login狀態(ChatRoomMain.py,第43行),顯示Login界面(ChatRoomMain.py,第47行)來輸入 暱稱與連線方式,選擇成為server端(建立聊天室)或是client端(加入),成功建立連線後進入chatRoom狀態 (ChatRoomMain.py,第157行),顯示聊天室介面(ChatRoomMain.py,第55行),連線失敗則仍停留在Login狀態。
於ChatRoom狀態下,當client端失去與server的連線時,回到Login狀態(ChatRoomMain.py,第96行),若server端失去與client的連線則仍停留在ChatRoom狀態。
server端與client端以各別的方式,在ChatRoom狀態下,處理聊天室的文字輸入與顯示流程。server端與client端間的傳輸協定的目標為執行命令的同步--server端與所有client端都顯示相同順序的文字。
詳細的server端與client端間的傳輸協定以及gui設計將稍後說明。
----
Panda3D的FSM介紹參考這裡。
當程式啟動時,進入Login狀態(ChatRoomMain.py,第43行),顯示Login界面(ChatRoomMain.py,第47行)來輸入 暱稱與連線方式,選擇成為server端(建立聊天室)或是client端(加入),成功建立連線後進入chatRoom狀態 (ChatRoomMain.py,第157行),顯示聊天室介面(ChatRoomMain.py,第55行),連線失敗則仍停留在Login狀態。
於ChatRoom狀態下,當client端失去與server的連線時,回到Login狀態(ChatRoomMain.py,第96行),若server端失去與client的連線則仍停留在ChatRoom狀態。
server端與client端以各別的方式,在ChatRoom狀態下,處理聊天室的文字輸入與顯示流程。server端與client端間的傳輸協定的目標為執行命令的同步--server端與所有client端都顯示相同順序的文字。
- server端收集所有的文字輸入,包含server端與所有client端的文字輸入,再將收集的文字輸入串傳送給所有client,server再將這些收集的文字輸入串顯示於自身gui上。
- client端送出自身的文字輸入給server端,如果收到server傳送的文字輸入串則顯示於自身gui。
詳細的server端與client端間的傳輸協定以及gui設計將稍後說明。
----
聊天室練習
以一個簡單的聊天室來練習panda3D中的2d gui 與 server - client 架構的網路設定。程式碼可以從這裡下載。
分成八個部份討論:
資料夾中包含以下檔案:
ChatRoom.py
執行點
ChatRoomMain.py
chat room主類別。於第II、IV、VIII段討論。
LoginFrame.py
login畫面組成。於第VI段討論。
ChatRoomFrame.py
chat room畫面組成。於第VI段討論。
ScrolledTextFrame.py
可捲動文字顯示框, 使用於chat room畫面組成中。於第VI段討論。
NetworkLib.py
網路操作模組。於第III段討論。
CommandInputLib.py
命令傳輸定義與函數。於第V、VIII段討論。
UnicodeDefine.py
unicode設定。於第VII段討論。
分成八個部份討論:
資料夾中包含以下檔案:
ChatRoom.py
執行點
ChatRoomMain.py
chat room主類別。於第II、IV、VIII段討論。
LoginFrame.py
login畫面組成。於第VI段討論。
ChatRoomFrame.py
chat room畫面組成。於第VI段討論。
ScrolledTextFrame.py
可捲動文字顯示框, 使用於chat room畫面組成中。於第VI段討論。
NetworkLib.py
網路操作模組。於第III段討論。
CommandInputLib.py
命令傳輸定義與函數。於第V、VIII段討論。
UnicodeDefine.py
unicode設定。於第VII段討論。
2010年2月3日 星期三
多個event handler接收同一個event的執行結果
panda3d的event可以參考
http://www.panda3d.org/wiki/index.php/Event_Handlers
以下例子是測試多個event handler接收同一個event的執行結果。
執行結果如下:
先產生10個不同的EventHandler物件,它們接收同一個event 'test'。
以messenger.send('test')發出event 'test'之後,這10個EventHandler物件都收到這個event,並執行各自的call back function。
從執行結果可以看到這10個EventHandler物件並不是依照被產生的順序接收到event,但每次執行的順序相同。
用event的方式可以分離產生event的物件,與執行此event的call back function物件。產生event的物件不必知道call back function函數名稱(參照),執行call back function的物件可以在執行期動態的指定(accept)或取消(ignore)對應event name的call back function,而不用改變產生event的物件。
缺點也在於它的優點,event機制如同全域變數,當兩個產生event的物件誤用了相同的event name時,會呼叫到其他接收相同event name的call back function,造成意外的錯誤。另外一個缺點在於,無法輕易的從產生event的地方得知此event的call back function為何,它可能藏在任何物件中,或是不存在。
http://www.panda3d.org/wiki/index.php/Event_Handlers
以下例子是測試多個event handler接收同一個event的執行結果。
from direct.showbase import DirectObject import direct.directbase.DirectStart class EventHandler(DirectObject.DirectObject): def __init__(self,id): self.accept('test',self.eventHandle) self.id = id def eventHandle(self): print "eventHandle in EventHandler(%d)"%(self.id) objectList = [EventHandler(i) for i in xrange(10)] messenger.send('test') run()
執行結果如下:
eventHandle in EventHandler(9)
eventHandle in EventHandler(8)
eventHandle in EventHandler(2)
eventHandle in EventHandler(7)
eventHandle in EventHandler(1)
eventHandle in EventHandler(6)
eventHandle in EventHandler(0)
eventHandle in EventHandler(5)
eventHandle in EventHandler(4)
eventHandle in EventHandler(3)
先產生10個不同的EventHandler物件,它們接收同一個event 'test'。
以messenger.send('test')發出event 'test'之後,這10個EventHandler物件都收到這個event,並執行各自的call back function。
從執行結果可以看到這10個EventHandler物件並不是依照被產生的順序接收到event,但每次執行的順序相同。
用event的方式可以分離產生event的物件,與執行此event的call back function物件。產生event的物件不必知道call back function函數名稱(參照),執行call back function的物件可以在執行期動態的指定(accept)或取消(ignore)對應event name的call back function,而不用改變產生event的物件。
缺點也在於它的優點,event機制如同全域變數,當兩個產生event的物件誤用了相同的event name時,會呼叫到其他接收相同event name的call back function,造成意外的錯誤。另外一個缺點在於,無法輕易的從產生event的地方得知此event的call back function為何,它可能藏在任何物件中,或是不存在。
訂閱:
文章 (Atom)