UCGUI窗體管理及消息處理機制分析
----多對話框/模態窗體/透明窗體支持分析
作者:ucgui
日期: 2005-09-08[v1.0.0.0 2005-06-30完成]
版本: v1.0.0.1
版本
修改說明
時間
v1.0.0.0
ü 實現UCGUI中多對話框支持。
2005-06-30
v1.0.0.1
ü 增加UCGUI中各種基本消息介紹 。
ü 增加窗體消息LOOP機制介紹。
ü 增加對話框結構說明及其消息LOOP處理移到M ainTask函數中由用戶處理的原因剖析,并詳細分析了對話框中按鈕的點擊消息傳送到用戶自定義對話框回調函數中處理的傳遞流程。
ü 增加外部輸入設備消息處理機制介紹。如滑動操作外設MOUSE及觸摸屏輸入消息(WM_TOUCH)的處理機制,及按鍵式操作外設消息(WM_KEY)處理機制。
ü 增加一種更簡單的多對話框支持的方法及說明。
ü 增加模態對話框實現原理分析。
ü 增加透明窗體實現原理分析。
2005-09-08
問題的提出:
[求助]關于對話框處理程序中,想在OK按鈕按下后想彈出一個消息框,該怎么做?直接加在程序中好像不行,如何讓消息框彈出后成為模態窗體呢?請版主幫幫忙。
[解析]在UCGUI中,對話框只支持單個對話框窗體,不支持多個獨立的對話框,現在我們從其源碼來分析一下它為什么支持單個對話框窗體以及如何改進它以支持多個獨立對話框,要講解這個問題我們必須首先理解UCGUI中的窗體消息LOOP,沒有消息LOOP窗體就是死水一潭,不能接受任何外界的輸入,只是一個畫在那里的圖畫而已。
[聲明]本文中提到的源碼均為UCGUI3.24版源碼,新版UCGUI源碼會有改動,請下載本文示例代碼來結合閱讀本文。
摘要: 本文主要介紹了UCGUI中的對話框的消息處理機制,并指出在現有UCGUI上如何增加多窗體支持,并在分析解決問題時著重介紹了其輸入設備消息WM_TOUTCH及WM_KEY兩類消息處理方法,并同時初步指出一種在UCGUI中實現模態對話框以及透明窗體的原理說明,不還有窗體重畫消息WM_PAINT消息處理原理。
一、各種基本消息介紹及處理流程----對話框內部消息流轉及外部消息LOOP分析.
UCGUI是采用的消息驅動的,它專門有對外的一套收集消息的接口, 我在模似器中, 就是通過LCD模擬顯示屏窗口的MOUSE消息,將MOUSE消息傳入到這個接口中, 以驅動UCGUI中的窗體的。
UCGUI中的消息驅動其實與WINDOWS的是類似的,幾種基本的消息與WINDOWS是一樣的,但UCGUI的更簡單且消息更少,對于一些消息的處理得也很簡化,沒有WINDOWS那么多的消息種類及復雜處理。在WINDOWS中,如我們處理按鈕控件的點擊事件的是在WM_COMMAND消息中,通過按鈕的標志ID來區分不同的按鈕,所以按鈕標志ID必須不同的,否則無法區別開(除非不在父窗體的WM_COMMAND消息中處理)。
UCGUI中一些基本的消息如下:
WM_CREATE---窗體創建消息,每創建一個窗體完后都會向該窗體發送此消息,如WM_CreateWindowAsChild創建完窗體均會發一此消息,但在UCGUI中對于此消息的很少處理,如果用戶想在對話框之后做些初始化操作或是創建其它子窗體的動作,可以處理此消息,不過對話框一般有專門的初始化消息WM_INIT_DIALOG,它是在創建對話框后發送的。
WM_SHOW-----顯示窗體消息,此消息在UCGUI中各控件窗體內均未作處理,如果你通過消息發送函數來發送這類沒有在UCGUI中各窗體中處理的消息,是沒有有什么響應的,不要感到奇怪。要顯示窗體一般是通過WM_ShowWindow()函數實現的,這個函數做的也就是改變窗體顯示標志[WM_SF_ISVIS],并使窗體矩形區域無效[WM_InvalidateWindow()]以產生重畫消息。
WM_SET_ENABLE---設置窗體不能使用消息,UCGUI中有一種復選框為不可改變的,但是這個功能也不完全,如果你對著UCGUI中的按鈕使用WM_DisableWindow()來設置其無效,按鈕照樣還是可以使用,不過要改進這些小毛病還是很容易的,這里只是提醒大家UCGUI中很多沒有實現的小地方,不要到時候使用時感到很奇怪,感覺到奇怪時最好去看看源碼,看看源碼中是否實現了此功能,不要郁悶。
WM_PAINT ----窗體重畫消息,當窗體所在區域全部或是部分區域無效時,系統將會發出該重畫消息,將無效區域重畫,但UCGUI中的處理比較簡單,都是將窗體全部區域重畫;如果用戶自己想在窗體上畫上一些信息,一般都在在該消息當中畫,UCGUI中的各種提供的系統控件都必須在其系統的提供的消息回調函數中處理此消息來畫出控件。當由外部輸入操作引起無效窗體區域產生時,系統都會在消息處理中發送該消息到窗體消息回調函數中,以重畫此窗體,在下面講解消息循環機制時將會著重講解到該消息的產生。
[透明窗體]---經常有朋友想知道在UCGUI中如何實現透明窗體,透明窗
窗體顯示在前臺時,可以看到部分位于其窗體后的內容,即透過窗體可 以看到窗體背后的圖象。在UCGUI中有關于透明窗體的設置選項,可是
沒有實現此功能,其實要實現原理如下:第一透明窗體及其所有子窗體
都必須透明處理;第二是對于所有有透明屬性的窗體,在繪圖時必須使
用透明填充功能的矩形填充函數,主要是修改窗體的WM_PAINT消息中畫
窗體時的矩形填充函數為透明的矩形填充;第三透明的矩形填充函數的
實現,通常情況下的矩形填充是以當前前景色來填充,那么關鍵就是實
現畫點函數的透明填充,要使一個透明,可以取當前顯存中存點的點的
RGB顏色,然后再與當前要畫的顏色按照一個比例進行混合得一個新的
RGB值,再將此值畫以屏幕上就可能實現透明填充的效果。
WM_TOUCH----處理類似MOUSE的滑動操作方式的輸入外設的消息,如觸摸屏一般都是將其消息從硬件接收到后轉化為該消息形式發送出去,該消息中必須包含消息在屏幕中的發生位置坐標及輸入設備狀態(按下狀態或彈起狀態),此消息在任務消息循環中循環處理,一旦產生就會發送給當前焦點窗體,在后面將詳細講解該消息的處理機制。
WM_KEY------處理類似KEY的按鍵式操作的輸入外設的消息,消息中必須包含按鍵的按下或彈起狀態,此消息也是在任務消息循環中循環處理,一旦產生就會發送給當前焦點窗體,講解消息LOOP時再詳細介紹。
WM_SET_FOCUS----講到剛才上面的兩個消息時,就反復提到了當前焦點窗體的概念,所有外部輸入設備消息都是發送給當前焦點窗體的,用戶可以通過此消息來設定當前的焦點窗體。外部輸入操作也會改變當前焦點窗體,如點擊某窗體時會在該窗體的WM_TOUCH消息處理中設置該窗體本身為當前焦點窗體;當在對話框中按鍵TAB鍵時,同樣也可以將焦點在對話框上各控件間切換,這是在對話框的WM_KEY消息中處理實現的[了解一下WM_SetFocusOnNextChild()函數],是根據創建對話框時指定的資源定義數組中的順序來切換的,并沒有WIN下面指定的TabIndex這樣一個值來指定次序的值。
WM_NOTIFY_PARENT---這個消息將子窗體的外設輸入的消息傳送到它的父窗體,因為一般的情況下消息都是在父窗體中統一處理的,如對話框中的按鈕點擊事件,一般都是在用戶自定義的窗體消息處理函數中處理,所以就必須要子窗體將獲取的輸入外設的消息傳送給父窗體,這樣才能在父窗體中進行子窗體的點擊事件消息的處理,這個消息的機制類似WIN下面的WM_COMMAND消息,處理該消息時通過控件ID來區別不同的控件,通過消息中的通知碼來區別控件被操作的各種狀態,具體這個消息的詳細說明請參見后面的分析。
WM_DELETE---要刪除窗體時發送的消息,主要清除窗體數據結構所占用內存,此消息主要由WM_DeleteWindow()函數發送了,如點擊OK按鈕關閉對話框時,最終會調用此函數來刪除窗體,不過UCGUI中沒有最大化最小化關閉等系統功能按鈕。最基礎窗體結構注解如下,在該結構中有兩個很重要的成員,hNextLin是記載窗體的下一個窗體,這個成員用于遍歷所有已經創建的窗體;hNext是記載窗體下一個兄弟窗體,這個成員用于遍歷每個窗體對應的子窗體;這個結構是最基礎,一般的控件在這個結構之上還會有一些擴展的結構,如按鈕對應有BUTTON_Obj結構。
typedef struct WM_OBJ_struct WM_Obj;
struct WM_OBJ_struct {
GUI_RECT Rect; /* 窗體矩形區域 */
GUI_RECT InvalidRect; /* 窗體無效矩形區域 */
WM_CALLBACK* cb; /* 窗體消息回調函數 */
WM_HWIN hNextLin; /* 窗體下一個窗體句柄*/
WM_HWIN hParent; /* 父窗體句柄*/
WM_HWIN hFirstChild; /* 第一子窗體句柄*/
WM_HWIN hNext; /* 下一個兄弟窗體句柄 */
U16 Status; /* 窗體當前狀態 */
};
WIDGET_HandleActive()—基礎控件共通消息處理,在大部分的UCGUI控件中都會在消息回調函數的頭部進行這個調用,如果處理了消息后,就直接退出消息回調函數的調用。這個函數中處理如下消息:
2 WM_GET_ID[返窗體控件標志ID].
2 WM_SET_FOCUS[設置當前窗體為焦點窗體,設置完后還必須向該窗體的父窗體發送一個WM_NOTIFY_CHILD_HAS_FOCUS消息讓其父窗體更新它記載的當前焦點子窗體].
2 WM_GET_HAS_FOCUS[獲取當前窗體是否為焦點窗體].
2 WM_SET_ENABLE[設置窗體為不可用窗體] .
2 WM_GET_ACCEPT_FOCUS[獲取當前窗體是否可設置為焦點窗體].
2 WM_GET_INSIDE_RECT[返回窗體內框矩形,如按鈕有3D效果時會有效果邊框寬度,內框矩形就是窗體矩形被邊框剪裁后的矩形].
WM_DefaultProc()----窗體默認消息處理函數,UCGUI中提供一些基礎的控件,這些控件有些共通的消息均在此處理,如下:
2 WM_GETCLIENTRECT[獲取窗體矩形區域,相對于矩形自身]
2 WM_GETORG[獲取窗體矩形左上角坐標].
2 WM_GET_INSIDE_RECT[獲取窗體矩形區域,相對屏幕].
2 WM_GET_CLIENT_WINDOW[獲取窗體客戶區子窗體句柄,如對話框的中的子窗體FrameWin即為此種窗體].
2 WM_KEY[銨鍵消息處理,通知父窗體子窗體的按鍵消息,有些控件自己要處理這個消息,如Edit控件處理完此消息后就沒有再調用WM_DefaultProc(),從而沒有將WM_KEY消息通父窗體;如Button控件,根本沒有對此消息進行處理,直接是通過默認處理發給了父窗體處理;有些控件如Checkbox自己處理該消息,同時也調用默認消息處量將此消息通知父窗本,此種消息源窗體為子控件,目標窗體為父窗體。如此處理WM_KEY消息完全是UCGUI中如此做,在WIN中并沒有這樣做].
2 WM_GET_BKCOLOR[獲取窗體背景色,在此未實現,返回0xfffffff值,但 FrameWin窗體實現了此消息處理].
在UCGUI的對話框的窗口消息處理函數中OK按鈕的點擊事件, UCGUI的處理方法與WIN下面是不同, 它在WM_NOTIFY_PARENT消息中處理[片段如下]:
case WM_NOTIFY_PARENT:
Id = WM_GetId(pMsg->hWinSrc); /* Id of widget */
NCode = pMsg->Data.v; /* Notification code */
switch (NCode) {
case WM_NOTIFICATION_RELEASED: /* React only if released */
if (Id == GUI_ID_OK) { /* OK Button */
GUI_MessageBox("This text is shown\nin a message box",
"Caption/Title", GUI_MESSAGEBOX_CF_MOVEABLE);
}
if (Id == GUI_ID_CANCEL) { /* Cancel Button */
GUI_EndDialog(hWin, 1);
}
break;
}break;
UCGUI中的消息種類不多, 只有差不多不到二十種,但對于嵌入式系統來說已經完全足夠了,用戶可以自定義消息(從WM_USER起)。 WM_NOTIFY_PARENT這個消息是由子窗體傳送給父窗體的, 由消息的名字也可以看出這一點,OK按鈕也是一個窗體,當MOUSE點擊在它上面時,UCGUI首先會傳遞一個WM_TOUCH消息到OK按鈕的窗口消息處理函數,OK按鈕是一個系統提供的控件,系統已經提供了一個默認的消息的窗口消息處理函數,這個函數會處理大部分的默認窗口消息并隨后將此消息轉發給父窗體,即WM_NOTIFY_PARENT消息,它是由函數WM_NotifyParent(hObj, Notification)實現的.
WM_TOUCH消息在按鈕的消息處理函數_BUTTON_Callback中的_OnTouch函數中處理,在處理過程完后會調用WM_NotifyParent向按鈕的父窗體發WM_NOTIFY_PARENT消息告訴對話框回調函數按鈕被點擊了,這個過程再說詳細一點是這樣的:
點擊OK按鈕.
產生按鈕WM_TOUCH消息.
UCGUI中的消息LOOP調用按鈕默認的按鈕窗口消息處理函數_BUTTON_Callback.
_OnTouch默認處理按鈕點擊并發送給父窗體WM_NOTIFY_PARENT消息,這里要注意MOUSE點擊后,有三種情況:第一種是點擊后在按鈕范圍內彈出MOUSE,這種情況下,會送的消息中還有一個通知碼就是WM_NOTIFICATION_RELEASED;第二種情況是點擊拖到按鈕范圍外彈起MOUSE,此時通知碼是WM_NOTIFICATION_MOVED_OUT;第三種情況是點擊后一直未彈起MOUSE的過程中消息通知碼為WM_NOTIFICATION_CLICKED;在這個函數中還會處理設置按鈕點擊后MOUSE至未彈起前的按下狀態,這樣在按鈕下一次畫出時就會以按下的狀態顯示出來.
默認的對話框窗體消息處理函數_FRAMEWIN_Callback收到WM_NOTIFY_PARENT消息并最終傳送該消息到用戶自己定義的對話框消息處理函數,這里要注意的一點是,其實對話框對話框主要是由一個FrameWin子窗體構成的,這個子窗體大小為對話框指定的大小,對話框上的其它控件是都是FrameWin的子窗體,由_FRAMEWIN_Callback傳送的消息首先是傳送到對話框的默認窗體消息回調函數_cbDialog,然后再經它傳送到用戶自定義的窗體回調函數當中。
用戶在自己的對話框消息處理函數中處理WM_NOTIFY_PARENT消息,即按鈕的點擊消息,該消息參數中含有按鈕的ID及操作狀態,如果通知碼是WM_NOTIFICATION_RELEASED,此時證明一次點擊事件完成。
void WM_NotifyParent(WM_HWIN hWin, int Notification){
WM_MESSAGE Msg;
Msg.MsgId = WM_NOTIFY_PARENT;
Msg.Data.v = Notification;
WM_SendToParent(hWin, &Msg);
}
這個函數相當簡單, 其主要還是WM_SendToParent這個函數的調用, 這個函數再調用void WM_SendMessage(WM_HWIN hWin, WM_MESSAGE* pMsg), 這個函數是最基本的一個消息發送處理函數, 它的第一個參數指定了接受這個要處理的消息的句柄, 第二個指定了是什么消息。這個函數的主要作用是調用相應窗口的消息處理函數來處理消息,如果你有消息要發送給指定的窗體處理,那么也可以使用這個函數。
在上面, 我們剛剛分析了在對話框內部消息處理的流轉,其中分析了我們在自己指定的對話框消息處理函數當中是如何可以獲得按鈕的點擊消息并進行處理的,現在我們就再來分析一下對話框外面的消息接收:首先是來了解一下GUI_ExecDialogBox函數,這個函數有幾個參數:
第一個是對話框的資源定義數組,這個數組定義了對話框的組成子窗體,其中數組第一個成員必須是FrameWin窗體,數組每一個成員記載了創建子窗體所用函數/子窗體Caption/子窗體標志ID/子窗體的位置及寬高/創建窗體時樣式標志/額外傳送的參數.
第二個參數是上述的數組的大小.
第三個參數是用戶指定的對話框窗體消息回調函數指針.
第四個參數是對話框的父窗體,默認為0.
第五、六參數指定對話框的左上角屏幕位置.
GUI_ExecDialogBox主要完成如下幾件事:
根據傳進來的對話框資源定義數組創建對話框及對話框中的子窗體.
根據傳進來的窗口消息處理函數,記載到一全局變量保存,當這個全局變量中記載的函數指針為非空時,執行消息LOOP,消息LOOP中會將當前的MOUSE及KEY消息發送給當前焦點窗體.
當對話框關閉時,記載對話窗體消息回調函數的全局變量會被清為0,此時消息LOOP就會退出,對話框結束.
二、發現存在的問題-----點擊OK后無論先關閉消息框還是對話框,另一個不再響應.
點擊對話框的OK后彈出消息框, 會出現當按下對話框的Cancel關閉對話框后, 彈出的消息框就沒有任何響應的情況. 或者是關閉掉彈出的消息框, 對話框就沒有任何響應的情形:從外部粗步分析的原因是調用MainTask的線程已經退出了, 這個線程是在模擬器中開啟的專門用于運行GUI任務的線程,它的線程函數是Thread, Thread函數里調用main,main中再調用MainTask,所以該線程退出后也就代表UCGUI任務已經結束了。這是從模擬器的角度來分析, 現在我們分析一下為什么MainTask的調用線程會這么早退出呢?
由我們第一節中關于GUI_ExecDialogBox所做的幾件中可以分析到, 當UCGUI中有一個獨立的窗體退出后_cb會被清為0, 此時退出GUI窗口LOOP. 即結束了UCGUI窗口消息處理。
其實, GUI_MessageBox彈出的消息框其實也是一種對話框, 這最終調用的還是GUI_ExecDialogBox,開始我們就分析過,進入這個函數后,會有一個全局變量記錄當前對話框窗體的消息處理函數指針,但是目前的問題如下:
已經建立了兩個這樣的對話框窗體,這樣一個全局變量來記載當前對話框的窗體消息處理函數指針顯然不夠,而且先前打開的對話框的的用戶指定的窗體消息回調函數已經不再被調用了,此時第一個對話框的由子窗體回傳到父窗體的消息均會傳到第二次打開的對話框的用戶指定的窗體消息回調函數中.
第二次彈出消息框再次進入GUI_ExecDialogBox中的 while循環后,先前的對話框中的while循環就被掛起了,直至第二次的GUI_ExecDialogBox中的 while循環退出,無論關閉消息框還是對話框,都會導致知退出第二次消息LOOP。第二次消息LOOP退出后返回點為彈出消息框后的下一句,直至返回到第一個對話框的while循環后退出GUI_ExecDialogBox.
但我們期待的結果是,點擊對話框的OK彈出消息框, 關閉掉對話框或是消息框,其它的都要對話框繼續有反應,下面我們就來分析一下如何達到這個目標,看看要做些什么具體的改動:
三、UCGUI中的消息LOOP處理分析-----尋找問題的解決辦法.
在我們發現這個問題, 我們已經粗步分析了,問題不是出在我們編寫程序上, 而上UCGUI的內部,那么要解決這個問題, 我們就要進一步了解UCGUI的窗口體系。其實換一句話說,在嵌入式應用中,窗口的強大直接決定到GUI系統的體積大小,并不是所有的情況都要有這種支持,當然我們希望在下一版本可以有多個對話框的直接支持。
創建對話框:
void MainTask(void)
{
GUI_Init();
WM_SetDesktopColor(GUI_RED);
WM_SetCreateFlags(WM_CF_MEMDEV);
GUI_ExecDialogBox(_aDialogCreate,GUI_COUNTOF(_aDialogCreate), &_cbCallback, 0, 0, 0);
}
上面是我們創建對話框的程序,是我們編寫的代碼, GUI_ExecDialogBox()這個函數的作用我們已經分析過了,它所做的事用一句話來說就是創建對話框并進入窗體消息LOOP處理,下面將詳細分析一下LOOP消息的處理流程:
int GUI_ExecDialogBox(const GUI_WIDGET_CREATE_INFO* paWidget,
int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,
int x0, int y0)
{
_cb = cb;
GUI_CreateDialogBox(paWidget, NumWidgets, _cbDialog,hParent, x0, y0);
while(_cb){
if (!GUI_Exec())
GUI_X_ExecIdle();
}
return _r;
}
這個LOOP類似我們非常熟悉的WIN下面的消息LOOP, 其原理是一致的. GUI_CreateDialogBox負責創建對話框的所有子窗體,特別注意它其中一個參數傳入是Dialog.c中定義的_cbDialog,這個函數什么也沒做,基本上是轉而調用_cb,后面我們會提到關于它的修改。_cb是對話框的用戶定義窗口消息處理函數,這里面有一個判斷,就是_cb非空時,才進行消息LOOP, _cb在Dialog.c中的定義為:[static WM_CALLBACK* _cb;] _cb是一個全局變量,我們程序中創建對話框與彈出消息框時兩次調用了GUI_ExecDialogBox,后一次的_cb將會把前面的值沖,它是用戶自定義的窗口消息處理函數。
在while中有判斷, 那么可見_cb是在GUI_Exec之中是有使用的,對話框的FrameWin子窗體消息流轉調如下面的所示,窗口消息處理函數是在WM_SendMessage中通過函數指針的調用中, 注意[]內部的就是真正被調用來處理消息的函數:
GUI_Exec-->GUI_Exec1-->WM_Exec-->WM_Exec1-->WM_HandlePID-->WM_SendMessage-->(*pWin->cb)(pMsg)[_FRAMEWIN_Callback]-->_OnTouch()-->(*cb)(pMsg)[_cbDialog]--> *_cb)(pMsg)[_MESSAGEBOX_cbCallback]
WM_HandlePID()------專門處理類似MOUSE的滑動操作外設消息的函數.
WM_SendMessage()----基層的發送消息的函數,即調用相對應的窗體的消息回調函數來處理消息.
現在講到了窗體消息LOOP,在窗體系統中最根本一點的就是對外部輸入消息的處理,窗體就是靠消息驅動的,其處理代碼如下:
int WM_Exec1(void){
if(WM_pfPollPID){/* Poll PID if necessary */
WM_pfPollPID();
}
if(WM_pfHandlePID){
if (WM_pfHandlePID())
return 1; /* We have done something ... */
}
if(GUI_PollKeyMsg()){
return 1; /* We have done something ... */
}
if(WM_IsActive && WM__NumInvalidWindows) {
WM_LOCK();
_DrawNext();
WM_UNLOCK();
return 1; /* We have done something ... */
}
return 0; /* There was nothing to do ... */
}
它主要完成如下幾件事:
Poll PID中Poll個詞準確的意思應該是統計/測試的意思,這里是調用用戶的統計測試滑動操作外設的一個接口,用戶可以通過WM_SetpfPollPID()函數來設置自己用于統計/測試滑動操作外設的具體函數。
處理滑動操作外設 WM_TOUCH消息,真正的處理是在函數WM_HandlePID()中處理的,在后面滑動外設消息處理流程時有詳細說明,在新版中更細分此消息為WM_PID_STATE_CHANGED/ WM_MOUSEOVER/ WM_TOUCH三種消息,其實在WIN下面類似消息的處理更為復雜,有移動/滾動/單擊DOWN及UP[左右鍵]/雙擊[左右鍵]等七八種MOUSE消息,而且這些消息又分為窗體體客戶區與標題區的差別,標題區的都會在消息上加上NC的前輟,如WM_NCLBUTTONUP標題區單擊彈起消息。從這里我們也可以看到UCGUI中非常簡化的處理,簡單得不能再簡單了,的確是一個微型的GUI圖形支持系統。
按鍵式外設消息處理,GUI_PollKeyMsg()函數在發現有新的按鍵消息生時會調用WM_OnKey()將消息發送到當前焦點窗體處理,如果一直處于按鍵按下狀態時則會將前按鈕的虛擬碼存在一全部變量中,以供GUI_GetKey()調用來返回當前按下鍵值。UCGUI中有一個外部的鍵盤接口,外界通過GUI_StoreKeyMsg()發送鍵盤消息給UCGUI以驅動鍵盤,在我的模擬器當中就是將LCD模擬顯示屏窗口的所有鍵盤消息通過GUI_StoreKeyMsg()傳送到UCGUI中以驅動鍵盤消息處理,關于鍵盤消息的處理UCGUI中也是來一個處理一個,沒有任何緩沖處理,如果某些按鈕消息處理用時過長,就會造成其后的一些按鍵消息丟失。
static int _Key; //記載當前按鍵,GUI_GetKey時返回此值
static int _KeyMsgCnt; //當前鍵盤消息數量
static struct {
int Key; //鍵盤虛擬碼…
int PressedCnt; //按鍵次數…
} _KeyMsg;
上面是鍵盤消息結構,UCGUI中以一個全局的_KeyMsg鍵盤變量記載當前最新鍵盤消息,當前按鍵值用_Key,每產生按下鍵時用GUI_StoreKey更新一次此值,UCGUI中沒有按鍵彈起消息的處理。
檢測是否有無效窗體,如果有無效窗體,則向該無效窗體發送重畫消息,有一個全局變量WM__NumInvalidWindows用于記載當前無效窗體的數目,在函數_DrawNext()中每次重畫一個無效窗體,查找無效窗體時是通過遍歷查找的方法,先前說過窗體基本結構中有一個成員hNextLin記載下一個窗體,就在是此處用于遍歷所有窗體,找出無效的窗體,發送WM_PAINT消息給窗體。注意這里每次畫一個窗體的原因就是為了不影響窗體的消息處理,如果在此處用時太多,會嚴重影響消息處理的反應速度。
了解了UCGUI中消息處理的具體流程,那么再來分析這個先前提到的問題:無論是消息框還是對話框哪一個先被關掉, 都會掉用GUI_EndDialog,將_cb被清為零,也就意味著消息LOOP到此結束了,所以后面另外一個未被關掉的當然不會再有任何響應了!
void GUI_EndDialog(WM_HWIN hWin, int r) {
_cb = NULL;
_r = r; //通知WM_Exec等消息LOOP返回…
WM_DeleteWindow(hWin);//free該窗體結構占用的內存…
}
現在我們可以得出一個結論:UCGUI中對話框的設計只支持單窗口的消息處理,如果要多窗口的支持,可以如同示例中一樣,啟用多任務支持,不然在單任務下一個MainTask中只能支持一個獨立窗體,但是如果我們只是為了要彈出一個消息框而啟動一個任務, 這未免太不實際。
了解UCGUI后初步修改路分析如下:
消息傳送-----經過詳細的分析,認識到在消息處理中創建一個對話框窗體后,必須建立一個消息LOOP處理,來向UCGUI中的窗口捕捉并傳送外設的輸入消息,消息的處理實質上是通過WM_SendMessage函數來調用相應的窗口的消息回調函數。
消息LOOP----如果創建多個對話框窗體,則會進入一個新的消息LOOP處理層而掛起原來的消息LOOP處理,要避免這種情況發生必須將消息LOOP移到MainTask之外,并在創建完所有對話框之后執行消息LOOP處理。
消息分發-----用一個數組將所有創建對話框的自定義消息回調函數存放起來,然后在對話框消息分布處(_cbDialog函數處)對應分發各個對話框的消息,要注意和解決的問題是,必須根據消息所對應用窗體來正確分布。
刪除窗體-----在清除獨立窗體時,必須將此對話框對應的用戶自定義的窗體消息回調函數清零,并清除該窗體與其它窗體的數據關系及其占用資源,使其退出消息處理。