UCGUI信息處理機制

1、UCGUI中的消息驅動其實與WINDOWS的是類似的,幾種基本的消息與WINDOWS是一樣的,但UCGUI的更簡單且消息更少,對於一些消息的處理得也很簡化,沒有WINDOWS那麼多的消息種類及複雜處理。在WINDOWS中,如我們處理按鈕控件的點擊事件的是在WM_COMMAND消息中,通過按鈕的標誌ID來區分不同的按鈕,所以按鈕標誌ID必須不同的,否則無法區別開(除非不在父窗體的WM_COMMAND消息中處理)。

2、UCGUI的常規信息
1) WM_CREATE—窗體創建消息,每創建一個窗體完後都會向該窗體發送此消息,如WM_CreateWindowAsChild創建完窗體均會發一此消息,但在UCGUI中對於此消息的很少處理,如果用戶想在對話框之後做些初始化操作或是創建其它子窗體的動作,可以處理此消息,不過對話框一般有專門的初始化消息WM_INIT_DIALOG,它是在創建對話框後發送的。

2) WM_SHOW-----顯示窗體消息,此消息在UCGUI中各控件窗體內均未作處理,如果你通過消息發送函數來發送這類沒有在UCGUI中各窗體中處理的消息,是沒有有什麼響應的,不要感到奇怪。要顯示窗體一般是通過WM_ShowWindow()函數實現的,這個函數做的也就是改變窗體顯示標誌[WM_SF_ISVIS],並使窗體矩形區域無效[WM_InvalidateWindow()]以產生重畫消息。

3) WM_SET_ENABLE—設置窗體不能使用消息,UCGUI中有一種複選框爲不可改變的,但是這個功能也不完全,如果你對着UCGUI中的按鈕使用WM_DisableWindow()來設置其無效,按鈕照樣還是可以使用,不過要改進這些小毛病還是很容易的,這裏只是提醒大家UCGUI中很多沒有實現的小地方,不要到時候使用時感到很奇怪,感覺到奇怪時最好去看看源碼,看看源碼中是否實現了此功能,不要鬱悶。

4) WM_PAINT ----窗體重畫消息,當窗體所在區域全部或是部分區域無效時,系統將會發出該重畫消息,將無效區域重畫,但UCGUI中的處理比較簡單,都是將窗體全部區域重畫;如果用戶自己想在窗體上畫上一些信息,一般都在在該消息當中畫,UCGUI中的各種提供的系統控件都必須在其系統的提供的消息回調函數中處理此消息來畫出控件。當由外部輸入操作引起無效窗體區域產生時,系統都會在消息處理中發送該消息到窗體消息回調函數中,以重畫此窗體,在下面講解消息循環機制時將會着重講解到該消息的產生。

5) WM_TOUCH----處理類似MOUSE的滑動操作方式的輸入外設的消息,如觸摸屏一般都是將其消息從硬件接收到後轉化爲該消息形式發送出去,該消息中必須包含消息在屏幕中的發生位置座標及輸入設備狀態(按下狀態或彈起狀態),此消息在任務消息循環中循環處理,一旦產生就會發送給當前焦點窗體,在後面將詳細講解該消息的處理機制。

6 )WM_KEY------處理類似KEY的按鍵式操作的輸入外設的消息,消息中必須包含按鍵的按下或彈起狀態,此消息也是在任務消息循環中循環處理,一旦產生就會發送給當前焦點窗體,講解消息LOOP時再詳細介紹。

Ø WM_SET_FOCUS----講到剛纔上面的兩個消息時,就反覆提到了當前焦點窗體的概念,所有外部輸入設備消息都是發送給當前焦點窗體的,用戶可以通過此消息來設定當前的焦點窗體。外部輸入操作也會改變當前焦點窗體,如點擊某窗體時會在該窗體的WM_TOUCH消息處理中設置該窗體本身爲當前焦點窗體;當在對話框中按鍵TAB鍵時,同樣也可以將焦點在對話框上各控件間切換,這是在對話框的WM_KEY消息中處理實現的[瞭解一下WM_SetFocusOnNextChild()函數],是根據創建對話框時指定的資源定義數組中的順序來切換的,並沒有WIN下面指定的TabIndex這樣一個值來指定次序的值。

7) WM_NOTIFY_PARENT—這個消息將子窗體的外設輸入的消息傳送到它的父窗體,因爲一般的情況下消息都是在父窗體中統一處理的,如對話框中的按鈕點擊事件,一般都是在用戶自定義的窗體消息處理函數中處理,所以就必須要子窗體將獲取的輸入外設的消息傳送給父窗體,這樣才能在父窗體中進行子窗體的點擊事件消息的處理,這個消息的機制類似WIN下面的WM_COMMAND消息,處理該消息時通過控件ID來區別不同的控件,通過消息中的通知碼來區別控件被操作的各種狀態,具體這個消息的詳細說明請參見後面的分析。

8) 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控件中都會在消息回調函數的頭部進行這個調用,如果處理了消息後,就直接退出消息回調函數的調用。這個函數中處理如下消息:

² WM_GET_ID[返窗體控件標誌ID].

² WM_SET_FOCUS[設置當前窗體爲焦點窗體,設置完後還必須向該窗體的父窗體發送一個WM_NOTIFY_CHILD_HAS_FOCUS消息讓其父窗體更新它記載的當前焦點子窗體].

² WM_GET_HAS_FOCUS[獲取當前窗體是否爲焦點窗體].

² WM_SET_ENABLE[設置窗體爲不可用窗體] .

² WM_GET_ACCEPT_FOCUS[獲取當前窗體是否可設置爲焦點窗體].

² WM_GET_INSIDE_RECT[返回窗體內框矩形,如按鈕有3D效果時會有效果邊框寬度,內框矩形就是窗體矩形被邊框剪裁後的矩形].

Ø WM_DefaultProc()----窗體默認消息處理函數,UCGUI中提供一些基礎的控件,這些控件有些共通的消息均在此處理,如下:

² WM_GETCLIENTRECT[獲取窗體矩形區域,相對於矩形自身]

² WM_GETORG[獲取窗體矩形左上角座標].

² WM_GET_INSIDE_RECT[獲取窗體矩形區域,相對屏幕].

² WM_GET_CLIENT_WINDOW[獲取窗體客戶區子窗體句柄,如對話框的中的子窗體FrameWin即爲此種窗體].

² WM_KEY[銨鍵消息處理,通知父窗體子窗體的按鍵消息,有些控件自己要處理這個消息,如Edit控件處理完此消息後就沒有再調用WM_DefaultProc(),從而沒有將WM_KEY消息通父窗體;如Button控件,根本沒有對此消息進行處理,直接是通過默認處理髮給了父窗體處理;有些控件如Checkbox自己處理該消息,同時也調用默認消息處量將此消息通知父窗本,此種消息源窗體爲子控件,目標窗體爲父窗體。如此處理WM_KEY消息完全是UCGUI中如此做,在WIN中並沒有這樣做].

² 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函數處)對應分發各個對話框的消息,要注意和解決的問題是,必須根據消息所對應用窗體來正確分佈。

Ø 刪除窗體-----在清除獨立窗體時,必須將此對話框對應的用戶自定義的窗體消息回調函數清零,並清除該窗體與其它窗體的數據關係及其佔用資源,使其退出消息處理。

四、對UCGUI源碼做出部分修改以實現多獨立窗口支持.

在第三節當中,我們通過進一步的分析源碼,大致找到了解決問題的辦法,但那只是理論上的指導,實際上的修改其實還會帶來其它的很多問題,因爲在UCGUI體系中,對其源碼作出改動,一定都會影響到其它的地方,現在我們就實際的源碼修改說明幾點要注意的問題:

實際上這個問題有兩種決辦法,第一種改動很小,只須進行很小的幾處改動,是在之後想到的方法,最先採用的是第二種方法,對Dialog.c進行多處比較大的改動,比較複雜,但是之所以還在此處列出,其原因是爲了讓大家更加清楚的瞭解UCGUI中的外設輸入消息處理機制,只有在瞭解了第二種方法的基礎上才能更好的理解第一種方法,否則對於第一種方法不能理解透。下面分別描述這兩種修改辦法:

第一種辦法:

Ø 將GUI_ExecDialogBox中的消息LOOP提出到 MainTask這個UCGUI應用當中,放在所有的對話框創建之後進行;並將在它當中增加一個創建對話框個數的變量用於統計當前已經創建的對話框數量,在Dialog.c當中有一個現成沒作什麼用的全局變量static int _r;可以改做此用.

Ø 將GUI_ExecDialogBox中建對話框時中調用GUI_CreateDialogBox所傳的窗體消息回調函數改成用戶自定義指定的,而非Dialog.c中中默認的_cbDialog,這個函數的作用就是調用用戶指定的對話框窗體消息回調函數,所以可以在創建對話框中直接傳用戶指定的消息回調函數。

Ø 在GUI_EndDialog當中相應的將當前對話框個數減少,每關閉一個對話框減一.

Ø 在Dialog.c增加一個函數用於返回當前已經創建的對話框個數GUI_ExecDialogNum(),用於判斷是否應該繼續進行消息LOOP處理。

須要修改的幾個函數修改成如下所示:

int GUI_ExecDialogBox(const GUI_WIDGET_CREATE_INFO* paWidget,

               int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,

               int x0, int y0)

{

GUI_CreateDialogBox(paWidget, NumWidgets, cb, hParent, x0, y0);

return ++_r;

}

void GUI_EndDialog(WM_HWIN hWin, int r) {

_cb = NULL;

_r–;

WM_DeleteWindow(hWin);

}

int GUI_ExecDialogNum()

{

return _r;

}

void MainTask(void)

{

GUI_Init();

WM_SetDesktopColor(GUI_RED);    WM_SetCreateFlags(WM_CF_MEMDEV);

GUI_ExecDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate), &_cbCallback, 0, 0, 0);

while(GUI_ExecDialogNum()) {

   if (!GUI_Exec())

       GUI_X_ExecIdle();

}  

} lUIks

第二種辦法:

1、將原來的_cb修改成一個結構爲new_cb的結構數組,首設定最多可創建10個對話框窗體:

typedef struct win_cb{

WM_CALLBACK* _cb; //用戶自定義消息回調函數..

WM_HWIN hwin;     //_cb消息函數對應的對話框窗口..

WM_HWIN hclient; //_cb消息函數對應的對話框FrameWin窗口客戶區...

}new_cb,*lpnew_cb;

//在_cb數組中當前可用元素位置.

static int dialog_pos = 0;

//最多可創建對話框窗體數目,其實可以改成支持無數個,但這裏作簡單處理

static int MAX_DIALOG = 10;

//檢查是否還有獨立窗體存在, 以決定是否退出消息LOOP…

int checkHasDialog();

//獲取當前可用於存放對話框的位置索引, 創建新對話框時調用.

int getDialogIndex(lpnew_cb lp_cb);

//對話框窗口數組,創建對話框後, 將其相關信息記載到該數組當中時,其成員//賦值必須注意幾個問題,在下面具體代碼中說明:

static new_cb _cb[10];

//新修改後的創建對話框的函數…

int GUI_ExecDialogBox(const GUI_WIDGET_CREATE_INFO* paWidget,

                 int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,

                 int x0, int y0)

{

dialog_pos = getDialogIndex(_cb);

if(dialog_pos != -1) _cb[dialog_pos]._cb = cb;

else return _r;

GUI_CreateDialogBox(paWidget,NumWidgets,_cbDialog,hParent,x0, y0);

return _r;

}

WM_HWIN GUI_CreateDialogBox(constGUI_WIDGET_CREATE_INFO* paWidget,int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,int x0, int y0)

{

WM_HWIN hDialog = paWidget->pfCreateIndirect(paWidget,hParent,x0, y0,cb);

WM_HWIN hDialogClient = WM_GetClientWindow(hDialog);

//加到GUI_CreateDialogBox中的,其餘不變…

_cb[dialog_pos].hwin = hDialog;

_cb[dialog_pos++].hclient = hDialogClient;

}

Ø getDialogIndex(_cb)—創建新的對話框窗體前,首先必須在對話框數組中查找空位置,如果對話框窗體已達最大數,則不可再創建對話框窗體,這裏只做的是簡單處理,沒有用到動態內存分配,主要是因爲是演示,讀者自己可以嘗試支持無限創建對話框窗體。

Ø _cb[dialog_pos]._cb = cb—對話框窗口的窗口消息處理函數必須在GUI_CreateDialogBox調用之前賦值,因爲在GUI_CreateDialogBox中就會用到這個窗口消息處理函數。

Ø _cb[dialog_pos]中的hwin等窗口句柄的處理加到創建對話框函數當中,千萬不要在調用創建對話框函數後再根據返回的對話框句柄來賦這個值,因爲在創建對話框函數中創建子窗體時就會調用到對話框消息處理函數,如果hwin此時未初始化,則在_cDialog()中就無法分發消息,這樣對話框中的子窗體都無法正確顯示的。

Ø GUI_ExecDialogBox中的窗口消息LOOP改爲放到MainTask中調用,因爲當我們把窗體都創建了之後,再來執行消息LOOP的話,可以避免前面創建獨立窗體的消息LOOP被後面創建的消息LOOP中斷的作用。如果我們要創建多個對話框窗體,那麼LOOP當中要分發消息的對象也就是多個窗體而不是其中的一個,所以要從執行單個對話框函數中拿出,由用戶來寫在圖形應用任務當中,如同WIN中主窗體的消息LOOP處理類似。抽出後如下代碼所示:

void MainTask(void)

{

GUI_Init();

WM_SetDesktopColor(GUI_RED);

WM_SetCreateFlags(WM_CF_MEMDEV);

GUI_ExecDialogBox(_aDialogCreate,GUI_COUNTOF(_aDialogCreate), &_cbCallback, 0, 0, 0);

while(checkHasDialog()){

if(!GUI_Exec())

 GUI_X_ExecIdle();

}

}

2、在第三節中第2點還說到,要分別調用各個對話框窗體的用戶自定義的窗口消息函數,必須注意消息的分發,也就根據消息中的窗體句柄在對應的對話框數組中查找並調用相應窗體的回調函數,下面我們看一下具體分發的代碼:

static void _cbDialog(WM_MESSAGE* pMsg)

{

char buf[100];

int i = 0;

WM_LOCK();

for(i = 0; i < MAX_DIALOG; i++){

if(_cb[i]._cb != (WM_CALLBACK*)0){

   if (WM__IsWindow(pMsg->hWin)){

       if(pMsg->hWin==_cb[i].hwin||_cb[i].hclient==pMsg->hWin){

          (*(_cb[i]._cb))(pMsg);

       }

   }     

}

WM_UNLOCK();

}

_cbDialog是在對話框的FRAMEWIN_cbClient中調用的形式如下:

if(cb){

pMsg->hWin = hParent;

(*cb)(pMsg);

}

每個獨立對話框窗體均是這樣,通過其FrameWin子窗體來調用用戶自定義的窗口消息處理函數,在分發消息時,其實只須要根據消息中的窗體句柄來分發,因爲我們對於每個對話框,均記載了它的窗體句柄及FrameWin子窗體句柄。所有創建的獨立窗體的消息均是在_cbDialog中順序進行處理的。

3、第三節中所說的第3點,獨立窗體退出的處理:

void GUI_EndDialog(WM_HWIN hWin, int r) {

int i = 0;

char buf[255];

if (!hWin) return;

WM_LOCK();

if (WM__IsWindow(hWin))

{

   for(i = 0; i < MAX_DIALOG; i++){

       if(hWin == _cb[i].hwin || _cb[i].hclient == hWin){

          _cb[i]._cb = NULL;

          _cb[i].hwin = 0;

          _cb[i].hclient = 0;

       }

   }

}  

WM_UNLOCK();

_r = r;

WM_DeleteWindow(hWin);

}

4、關於類似MOUSE的滑動操作外設消息的處理函數WM_HandlePID().

消息LOOP中做的最重要的一件事是獲取消息並分發到相應窗體進行處理,這當中當然包括外設輸入消息的獲取與處理,WM_HandlePID()函數就是做這種處理的,它在WM_Exec1中調用。

這個函數是專門負責處理類似MOUSE的滑動操作外設消息,UCGUI中統稱爲WM_TOUCH消息,在GUI\Core\WMTouch.c文件當中,當你點擊或是在觸摸屏上按下時均會產生此消息。它當中有兩個變量:一個靜態的舊消息變量;一個是局部新消息變量。每次均從消息獲取接口GUI_PID_GetState()中取當前WM_TOUCH消息,處理時會比較新舊消息發生的屏幕座標的及外設操作的狀態(按下與否)以決定是否處理該消息,每次處理完最新消息後就將最新消息更新到舊消息變量上以避免對相同消息的重複處理,正是基於這一點纔會將對話框內的消息LOOP移到MainTask中進行。下面將詳細分析如此處理的原因及不如此處理會引發的問題。

五、UCGUI中滑動外設輸入消息的處理機制----外設輸入消息處理流程及模態對話框框的實現原理初步分析.

UCGUI中的的外設輸入消息統一稱爲WM_TOUCH,WM_HandlePID()就是專門處理這種消息的,如MOUSE及觸摸屏等滑動操作外設的消息處理,這種滑動外設消息的處理特徵爲:一是必須傳送消息發生點的屏幕X/Y座標;二是滑動外設按下與否的操作狀態。WM_TOUCH消息的處理流程如下:

1.通過GUI_PID_GetState獲取一個GUI_PID_STATE結構的WM_TOUCH消息,消息的獲取比較簡單,在GUI\Core\GUI_PID.C文件當中處理,這個文件提供的是Pointer input device[指針輸入設備]的輸入消息處理,但我認爲將看作爲滑動操作外設更爲貼切些,文件中提供有以下幾個函數:

Ø GUI_PID_Load--------設定處理此類設備輸入消息的處理函數,這裏爲WM_HandlePID().

Ø GUI_PID_GetState----獲取設備最新輸入的消息,UCGUI中處理得很簡單,用一個全局變量保存當前最新輸入的消息,返回此全局變量的一個COPY即可.

Ø GUI_PID_StoreState–設定設備最新輸入消息,其實就是設定一個全局變量_State的值,這個函數通常在設備消息接收的任務當中調用,在模擬器中它是在LCD模擬顯示窗口的MOUSE消息中調用,這裏必須說明的一個問題是,如果UCGUI在處理某些WM_TOUCH消息時用去時間過多的話,那麼就會丟失掉部分WM_TOUCH消息,因爲UCGUI只是用一個變量來接收消息而沒有消息隊列或是一個數組來緩存未處理的消息,如果使用隊列那麼必然會使消息處理更加複雜,所以如果需要在WM_TOUCH消息中處理一個用時比較長的操作,那麼最好使用一個新建的任務來完成該操作,否則在此過程中不能再處理任何外設的其它的輸入操作,這裏與WIN下面是一樣的,不過WIN下面單任務情況下只是操作不能及時有反應,但不會丟失後面操作所產生的消息,因爲它有消息隊列,在此我們也可以看到UCGUI中很多處理機制一切重簡.

Ø GUI_PID_Init--------初始化設備相關,此處函數爲空.

2.將新獲取的消息與函數內靜態的舊消息變量進行比較,包括該消息發生點的屏幕座標及外設操作狀態(是否按下):

滑動操作外設消息結構如下:

typedef struct{

int x,y; //消息發生點在屏幕中的x/y座標…

unsigned char Pressed; //滑動外設是否按下…

} GUI_PID_STATE;

3.如果支持圖形鼠標,則根據新消息屏幕位置設置當前使用的鼠標位置,如果此處不進行這個設置,則在打開MOUSE支持後,MOUSE是無法移動的,在觸摸屏當中一般不要求畫出圖形鼠標,一般只有在PS/2鼠標設備時才必須畫出圖形鼠標,否則無法進行鼠標操作。

4.當新舊消息的操作狀態比較發生變化時處理該消息[通過將消息中的外設按下與否的狀態相或的值是否爲1來判斷,爲1則表示狀態改變],否則不發送此消息到窗體進行處理,而是直接更新新消息到舊消息上,這裏我說的情況是UCGUI3.24版源碼的處理情況,但是看現在最新的有源碼的3.90源碼當中就處理了此種情況下的消息爲WM_MOUSEOVER,還有一個不同就是將操作狀態的變化[即新舊消息中的是否按下不相等時]作爲一個獨立的消息WM_PID_STATE_CHANGED來發送,除此之外的消息才處理爲WM_TOUCH消息,但是隻要真正理解了UCGUI3.24版源碼中消息處理的根本原理,理解新版源碼中的消息處理也是很容易的,其本質並未變化,只是將消息分成了三種處理,這樣顯得更加明確。

5.構造WM_TOUCH消息所需用要的數據,首先要獲取到當前焦點窗體句柄,當前焦點窗體是用WM__hCapture的全局變量記載,如果爲0則調用函數WM_Screen2hWin()根據消息發生點的屏幕座標來獲取WM_TOUCH消息的對應的窗體句柄,注意這個函數是根據窗體數據結構,從第一個創建的窗體WM__FirstWin開始來遞歸查找,先查找窗體的第一個子窗體,然後查找hNext兄弟窗體,要注意理解此函數,其遞歸查找的本質就是按照屏幕上的窗體層疊次序,從最上層窗體開始查找,窗體的前後臺順序是由父子及兄弟關係決定的,即UCGUI窗體層疊規則爲:窗體顯示在其下一個兄弟窗體[hNext]之前,父窗體顯示在子窗體之後;調整窗體的前後臺順序其實就是調整窗體在兄弟窗體間的位置,注意這裏要遞歸的理解這句話。

6.比較新舊焦點窗體變量,初始舊焦點窗體變量值爲0,每當處理消息後,如果是按下狀態的WM_TOUCH消息,則會將當前焦點窗體句柄更新到舊焦點窗體變量中保存;如果不是按下狀態的WM_TOUCH消息,則會在處理完消息後將舊焦點窗體變量賦爲0值。這樣處理的原因如下:當操作外設沒有處於按下狀態,此時進行滑動操作,無論是當前焦點窗體變化與否,都無須接受窗體變化時做額外的操作,可以想象將MOUSE在非按下狀態下從按鈕範圍內滑出的情況,這中間無須做特別的處理;但是如果是在操作外設處於按下狀態時進行沒動操作, MOUSE滑出到按鈕外後,就須要發一條消息通知按鈕改變爲非按下狀態[否則按鈕一直處於按下狀態],即消息中新舊焦點窗體變量不同且舊窗體變量值非0時,會向舊焦點窗體發送一個消息,這個消息分兩種情況:一種是滑出前鬆開按鈕並且下一個接收到的消息就是新焦點窗體的消息[這種情況發生在邊界處],此時新消息爲非按下狀態,則發送舊窗體一個未按下狀態的消息;另一種是滑出後依然未鬆開按鈕的,則發送給舊窗體一個消息數據爲0的消息。所以在UCGUI的消息處理中有關於當前焦點窗體與舊焦點窗體的比較,如果不同且舊焦點窗體不爲0,則必鬚髮送一外設不在處於按下狀態的消息告訴舊焦點窗體,否則舊焦點窗體一直處於外設按下狀態。

7.向當前焦點窗體發送消息,然後根據對於按下狀態的消息更新舊焦點窗體變量值爲當前焦點窗體,否則置其值爲0。

8.更新當前消息到舊消息變量中,結速本次消息處理。

以上分析了UCGUI中滑動外設輸入消息的基本處理流程,正是基於這種簡單的消息處理機制,導致產生這樣一個問題: 如果上述修改中我們沒有將窗口消息LOOP放到GUI_ExecDialogBox函數之外,那麼存在一個問題就是在點擊OK一次後會重複創建N多的消息框且N不定,原因是對相同的消息進行了重複處理。不是每處理完一次消息就更新了舊消息嗎?爲什麼還會重複處理?這裏面根本的問題就是舊消息還未更新;導致舊消息還未更新的原因有兩個: 第一是新的消息框彈出後進入新的消息框窗體的LOOP,從而掛起了上一次窗體LOOP處理使得無法進行舊消息的更新,因爲消息是在處理完後之更新的,但不幸的是上一次窗體消息LOOP處理完點擊消息後就再也沒能返回而是進入了新一層的消息LOOP(消息框的);第二是在MOUSE點擊消息到下一個新的消息產生這段時間內,有一個時間過程,因爲只有在移動MOUSE等操作之後才產生新的消息的,在這個過程中舊消息由於第一個原因中所說的,未能進行更新,所以一直還是按下狀態的MOUSE消息,所以在這段時間內進行了多少次重複處理,取決於何時產生新的消息,如果用戶點擊OK按鈕後快速移動MOUSE操作,則只會產生有限幾次的重複處理;如果說此後用戶再也沒有移動MOUSE的操作,那麼這個消息的重複處理一直會進行到用完所有可分配的動態內存,因爲這個過程中一直在創建消息框窗體,創建窗體需要動態分配的內存,在UCGUI中動態分配的內存是有限的,所以點擊彈出消息框後一直不進行MOUSE的任何操作會導致UCGUI中的動態內存用盡爲止。

解決這個消息更新之前的重複處理問題有二種辦法:第一種是將消息LOOP放出GUI_ExecDialogBox之外,即放入MainTask當中則不存在此問題,因爲創建消息框後不會進入到新的循環當中去,它會馬上進行舊消息的更新;第二種是改在WM_SendMessage之前就更新舊消息,這樣也可以避免由於舊消息未更新而重複處理同一消息。

以上就一個產生的問題分析了這個消息處理的函數,這其實也是這個消息處理函數的基本原理,瞭解以上就可以瞭解UCGUI中簡單的外設輸入消息(WM_TOUCH)的處理機制。比如說如果要實現模態對話框,則必須瞭解這個WM_TOUCH消息的處理,模態窗體是指它打開顯示之後就不能切換到其它窗體的進行工作的窗體,只有關閉它之後才能切換回別的窗體。下面簡單探討一下模態對話框的實現原理:

模態對話框分兩種情形:一種是指單個應用中的模態對話框,在WIN下面對於一個單獨的應用程序,模擬對話框則是這個應用中的最前臺窗體,只有在它關閉之後,應用中的其它窗體才能切換到前臺進行操作;一種是整個系統中的模態窗體,這種窗體位於系統中所有應用之前,在它關閉之前,系統中其它應用的所有窗體都不能切換到前臺接受操作。

單個應用中的模態對話框的實現:例如在創建一對話框A後,點擊OK後彈出消息框B,要求在B關閉之前A不能接受MOUSE操作的輸入,這裏其實要做的就是:在WM_TOUCH消息的處理時只接受B窗體的消息處理,除開B窗體之外其它的A窗體及其子窗體(遞歸處理)均不得在A存在之前進行WM_TOUCH消息的處理。如果要實現窗體A對應模擬窗體B,窗體C對應模態窗體D這樣相對關係的模態窗體處理,也是比較容易的,不過是要多記載一些模態窗體所相對的窗體信息。具體的模態對話框的實現就不在這裏進行詳細講解了,有時間會專門在獨立的文章中介紹具體的修改。

修改後的源代碼下載:

https://www.ucgui.com/ucgui/GUISim1004_MultDialog.rar

附言:

一般情況下,小型的GUI體系,都很少會有打個多個獨立窗口的要求,UCGUI還是在發展中,而且並不成熟, 所以有很多的問題,不完善是肯定的.

我們要學習UCGUI,首先一定要動手寫UCGUI的程序,閱讀源碼,這樣才以發現更多的問題,才能更瞭解UCGUI。我本人就是經常有問題就讀源碼,在實際編程中,發現的問題是最多的,而且現在模擬器已經還原成源碼,底下不再有任何的祕密,所有的問題都擺在源碼之下,所以只要花心思研究,應該可以解決很多問題,學習到很多圖形處理的深層知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章