原發(fā)于www. @2007-7-4
轉(zhuǎn)帖請(qǐng)注明出處【獻(xiàn)給初學(xué)者,若您是高手請(qǐng)不吝賜教,甚為感激!】
把Msg的結(jié)構(gòu)體寫在最前面:
typedef struct {
int MsgId;
/* type of message */
WM_HWIN hWin; /* Destination window */
WM_HWIN hWinSrc; /* Source window */
union {
const void*
p; /* Some messages need more info ... Pointer is declared "const"
because some systems (M16C) have 4 byte const, byte 2 byte default ptrs
*/
int v;
GUI_COLOR Color;
} Data;
}
WM_MESSAGE;
首先跟蹤一個(gè)按鍵發(fā)送函數(shù)
void GUI_SendKeyMsg(int Key,
int PressedCnt);
--》
WM_OnKey(Key, PressedCnt)
int WM_OnKey(int Key,
int Pressed) {
int r = 0;
WM_MESSAGE Msg;
WM_LOCK();
if
(WM__hWinFocus != 0) {
WM_KEY_INFO Info;
Info.Key = Key;
Info.PressedCnt = Pressed;
Msg.MsgId = WM_KEY;
Msg.Data.p =
&Info;
WM__SendMessage(WM__hWinFocus,
&Msg);
r = 1;
}
WM_UNLOCK();
return
r;
}
注意,消息發(fā)送到了WM__hWinFocus
--》WM__SendMessage(WM__hWinFocus,
&Msg);
void WM__SendMessage(WM_HWIN hWin, WM_MESSAGE* pMsg) {
WM_Obj* pWin = WM_HANDLE2PTR(hWin);
pMsg->hWin = hWin;
if
(pWin->cb != NULL) {
(*pWin->cb)(pMsg);
}
else {
WM_DefaultProc(pMsg);
}
}
消息發(fā)到了哪兒?yeah,就是我們直觀看到的當(dāng)前焦點(diǎn)--button
調(diào)用其回調(diào)函數(shù),一個(gè)KEY類型的消息,如下
void BUTTON_Callback(WM_MESSAGE *pMsg)
void
BUTTON_Callback(WM_MESSAGE *pMsg) {
BUTTON_Handle hObj =
pMsg->hWin;
BUTTON_Obj* pObj = BUTTON_H2P(hObj); (1)
/*
Let widget handle the standard messages */
if (WIDGET_HandleActive(hObj,
pMsg) == 0) {
return;
}
switch (pMsg->MsgId)
{ (2)
#if
BUTTON_REACT_ON_LEVEL
case WM_PID_STATE_CHANGED:
_OnPidStateChange(hObj, pObj, pMsg);
return; /* Message handled.
Do not call WM_DefaultProc, because the window may have been destroyed
*/
#endif
case WM_TOUCH:
_OnTouch(hObj, pObj, pMsg);
return; /* Message handled. Do not call WM_DefaultProc, because the window
may have been destroyed */
case WM_PAINT:
GUI_DEBUG_LOG("BUTTON:
_BUTTON_Callback(WM_PAINT)\n");
_Paint(pObj, hObj);
return;
case WM_DELETE:
GUI_DEBUG_LOG("BUTTON:
_BUTTON_Callback(WM_DELETE)\n");
_Delete(pObj);
break; /*
No return here ... WM_DefaultProc needs to be called */
#if 0 /* TBD:
Button should react to space & Enter */
case
WM_KEY:
{
int PressedCnt =
((WM_KEY_INFO*)(pMsg->Data.p))->PressedCnt;
int Key =
((WM_KEY_INFO*)(pMsg->Data.p))->Key;
if (PressedCnt > 0) {
/* Key pressed? */
switch (Key) {
case '
':
_ButtonPressed(hObj, pObj);
return;
}
} else {
switch (Key) {
case '
':
_ButtonReleased(hObj, pObj,
WM_NOTIFICATION_RELEASED);
return;
}
}
}
break;
#endif
}
WM_DefaultProc(pMsg);
}
(1)由句柄獲得對(duì)象
(2)判斷消息類型,本次發(fā)送消息為一個(gè)按鍵類型
直接忽略其他的消息,看case WM_KEY標(biāo)簽
case WM_KEY:
{
int PressedCnt =
((WM_KEY_INFO*)(pMsg->Data.p))->PressedCnt; (1)
int Key =
((WM_KEY_INFO*)(pMsg->Data.p))->Key; (2)
if (PressedCnt
> 0) { /* Key pressed? */ (3)
switch (Key)
{
case ' ':
_ButtonPressed(hObj, pObj);
return;
}
} else {
switch (Key) {
case ' ':
_ButtonReleased(hObj, pObj,
WM_NOTIFICATION_RELEASED);
return;
}
}
}
break;
(1-2)局部變量,獲得按下的key和狀態(tài)
這個(gè)函數(shù)有點(diǎn)兒令人匪夷所思,如果按下空格,則執(zhí)行_ButtonPressed(hObj,
pObj);
如果是釋放空格,則執(zhí)行_ButtonReleased(hObj, pObj,
WM_NOTIFICATION_RELEASED);
這兩個(gè)函數(shù)是做什么的?
這些都是private函數(shù),對(duì)外不提供接口
ucgui利用c的特性,很好的模擬了封裝、繼承等等面向?qū)ο筇匦?br>這里的private實(shí)現(xiàn)很好理解
類實(shí)現(xiàn)代碼中實(shí)現(xiàn)的行為不在其頭(.h)文件中提供聲明
其它通過包含頭文件使用這個(gè)類的函數(shù)由于沒有某些函數(shù)的聲明故不能調(diào)用
就完成了private特性
首先是_ButtonPressed(BUTTON_Handle hObj, BUTTON_Obj*
pObj)
這個(gè)函數(shù)比較容易理解,更改外觀的同時(shí)將消息上泵,通知自己的父窗體,自己的模樣被更新了
static void
_ButtonPressed(BUTTON_Handle hObj, BUTTON_Obj* pObj) {
WIDGET_OrState(hObj, BUTTON_STATE_PRESSED); (1)
if
(pObj->Widget.Win.Status & WM_SF_ISVIS) { (2)
WM_NotifyParent(hObj, WM_NOTIFICATION_CLICKED); (3)
}
}
(1)首先將這個(gè)Button對(duì)象的狀態(tài)設(shè)置成按下的
(2)看看這個(gè)Button對(duì)象的狀態(tài)是不是可見的(若在C++中,可以實(shí)現(xiàn)pObj->Status,不需要利用Widget.Win標(biāo)簽但這里我們可以清楚地看出類中組合關(guān)系)
(3)如果是可見的,就通知父窗體,觸發(fā)一個(gè)單擊事件。
void
WM_NotifyParent(WM_HWIN hWin, int Notification) {
WM_MESSAGE Msg;
Msg.MsgId = WM_NOTIFY_PARENT;
Msg.Data.v = Notification;
WM_SendToParent(hWin,
&Msg);
}
我們知道,Button的父窗體是Framewin中的Client。(注意,F(xiàn)ramewin和Client對(duì)象都有回調(diào)函數(shù),但Button的父窗體是Client,所以執(zhí)行的不是Framewin而是Client的回調(diào)函數(shù).)如此,一個(gè)按下消息發(fā)給了Client,如果Client中不進(jìn)行攔截,則將傳給用戶定義的
接口回調(diào)函數(shù)。
再讓我們看看釋放函數(shù):
static
void _ButtonReleased(BUTTON_Handle hObj, BUTTON_Obj* pObj, int Notification)
{
WIDGET_AndState(hObj, BUTTON_STATE_PRESSED);
if
(pObj->Widget.Win.Status & WM_SF_ISVIS) {
WM_NotifyParent(hObj,
Notification);
}
if (Notification == WM_NOTIFICATION_RELEASED)
{
GUI_DEBUG_LOG("BUTTON: Hit\n");
GUI_StoreKey(pObj->Widget.Id);
}
}
后面一個(gè)if是陌生的
if
(Notification == WM_NOTIFICATION_RELEASED) {
GUI_DEBUG_LOG("BUTTON:
Hit\n");
GUI_StoreKey(pObj->Widget.Id);
}
此處我不是很理解,當(dāng)按下-釋放過程結(jié)束時(shí),就證明一個(gè)按鍵工作結(jié)束,將這個(gè)按鍵的接受者的Id放到系統(tǒng)的Key隊(duì)列中去?(后經(jīng)試驗(yàn)發(fā)現(xiàn),目的大概為了保護(hù)按鍵信息,以便后期處理。)
綜上我們已經(jīng)可以分析一個(gè)按鍵消息通過系統(tǒng)的路徑(以Button為例)
按鍵--》找到focus窗體,將消息發(fā)送給它--》在這個(gè)窗體的回調(diào)函數(shù)中,如果定義了這個(gè)按鍵值,則將其攔截并工作
(以按鍵的回調(diào)函數(shù)為例,如果按鍵了,則將這個(gè)消息以NOTIFYPARENT的形式向上泵,知道用戶定義的接口回調(diào)函數(shù))
如果button回調(diào)函數(shù)沒有攔截這個(gè)按鍵值呢?
WM_DefaultProc(pMsg);
void WM_DefaultProc(WM_MESSAGE* pMsg) {
WM_HWIN hWin = pMsg->hWin;
const void *p = pMsg->Data.p;
WM_Obj* pWin = WM_H2P(hWin);
/* Exec message */
switch
(pMsg->MsgId) {
case WM_GET_INSIDE_RECT: /* return client window
in absolute (screen) coordinates */
WM__GetClientRectWin(pWin,
(GUI_RECT*)p);
break;
case WM_GET_CLIENT_WINDOW: /* return
handle to client window. For most windows, there is no seperate client window,
so it is the same handle */
pMsg->Data.v = (int)hWin;
return; /* Message handled */
case
WM_KEY:
WM_SendToParent(hWin, pMsg);
return; /* Message handled */
case
WM_GET_BKCOLOR:
pMsg->Data.Color = GUI_INVALID_COLOR;
return; /* Message handled */
case
WM_NOTIFY_ENABLE:
WM_InvalidateWindow(hWin);
return; /* Message handled */
}
/* Message not
handled. If it queries something, we return 0 to be on the safe side. */
pMsg->Data.v = 0;
pMsg->Data.p =
0;
}
注意,這里對(duì)于按鍵,直接向上泵(parent)
case WM_KEY:
WM_SendToParent(hWin, pMsg);
return; /* Message
handled
*/
這兒的句柄hWin還是Button,向上泵給Client,再泵給用戶定義回調(diào)函數(shù),最終在那里結(jié)束。
此處有一點(diǎn)提示,因?yàn)橛脩艋卣{(diào)函數(shù)中通常也會(huì)用到WM_DefaultProc這個(gè)過程處理不攔截的鍵,那么會(huì)不會(huì)產(chǎn)生死循環(huán)呢?
當(dāng)然不會(huì),因?yàn)樵谖覀冏鯳M_SendToParent函數(shù)的時(shí)候,MSG的目標(biāo)句柄已經(jīng)變成了Client窗體,而在Client中直接泵給用戶定義接口時(shí),
而在Client的回調(diào)函數(shù)中,又巧妙地將MSG的目標(biāo)句柄編程了FRAMEWIN,這樣在我們定義的接口回調(diào)函數(shù)中,我們收到的是一個(gè)發(fā)送到FRAMEWIN的消息。這里聰明地使Client
窗體這一層對(duì)用戶透明了。
看到這里,再回頭看一個(gè)預(yù)編譯標(biāo)志#if
0
我們可以確定的是,在Button控件中壓根不攔截WM_KEY消息,而是直接提供給DefaultProc中,通過DefaultProc泵給Client,隨后再直接提供給
用戶定義的接口回調(diào)函數(shù)。
知道了這樣的一個(gè)消息傳遞過程,我們就可以放心大膽的在自己定義的接口回調(diào)函數(shù)中定義對(duì)各種各樣的鍵進(jìn)行處理的過程了
case
WM_KEY就可以攔截到傳遞給Button的所有按鍵,但怎么判斷傳來的值是來自Button呢?我們回過頭去看那個(gè)被傳遞的MSG的起源:
WM_MESSAGE Msg;
WM_LOCK();
if (WM__hWinFocus != 0) {
WM_KEY_INFO Info;
Info.Key = Key;
Info.PressedCnt =
Pressed;
Msg.MsgId = WM_KEY;
Msg.Data.p = &Info;
糟糕,沒有定義hWinSrc,我們無法從消息中判斷信息來源了!?當(dāng)然可以,消息是發(fā)送到WM__hWinFocus的,而在消息傳遞的過程中沒有改變這個(gè)
全局的指針,我們可以大膽的把WM__hWinFocus的句柄拿來操作。但得到的只是一個(gè)句柄,如何知道它是Button?還是EditBox還是(*&……&?
uCGUI提供了一個(gè)由句柄得到Id的函數(shù)
int
WM_GetId (WM_HWIN
hWin);
反過來,從Id也可以得到控件的句柄,用的是函數(shù)
其中第一個(gè)參數(shù)是控件屬于的窗體句柄,
WM_HWIN
WM_GetDialogItem (WM_HWIN hWin, int Id);
是在內(nèi)存管理上做的文章
我認(rèn)為,這里應(yīng)該是模仿windows編程中的句柄-id-對(duì)象互相調(diào)用的幾個(gè)函數(shù)
這個(gè)函數(shù)中使用了遞歸,去查詢所有hWin的子窗體
言歸正傳,還是看WM_GetId:
int
WM_GetId(WM_HWIN hObj) {
WM_MESSAGE Msg;
Msg.MsgId =
WM_GET_ID;
WM_SendMessage(hObj, &Msg);
return
Msg.Data.v;
}
發(fā)送一個(gè)消息,像句柄索要它的Id
在觀察Button的callback時(shí),發(fā)現(xiàn)沒有現(xiàn)式地?cái)r截WM_GET_ID這一消息,但根據(jù)常識(shí),這樣一個(gè)標(biāo)準(zhǔn)接口不應(yīng)該在最終的實(shí)體化的類中實(shí)現(xiàn),
應(yīng)該由它的父類或祖父類實(shí)現(xiàn),仔細(xì)觀察,注意到這一句:
if (WIDGET_HandleActive(hObj, pMsg) == 0) {
return;
}
以下是定義:
int WIDGET_HandleActive(WM_HWIN hObj, WM_MESSAGE*
pMsg) {
int Diff, Notification;
WIDGET* pWidget =
WIDGET_H2P(hObj);
switch (pMsg->MsgId) {
case
WM_WIDGET_SET_EFFECT:
Diff = pWidget->pEffect->EffectSize;
pWidget->pEffect = (const WIDGET_EFFECT*)pMsg->Data.p;
Diff -=
pWidget->pEffect->EffectSize;
_UpdateChildPostions(hObj,
Diff);
WM_InvalidateWindow(hObj);
return
0; /* Message handled -> Return */
case
WM_GET_ID:
pMsg->Data.v = pWidget->Id;
return
0; /* Message handled -> Return */
case
WM_PID_STATE_CHANGED:
if (pWidget->State &
WIDGET_STATE_FOCUSSABLE) {
const WM_PID_STATE_CHANGED_INFO * pInfo =
(const WM_PID_STATE_CHANGED_INFO*)pMsg->Data.p;
if
(pInfo->State) {
WM_SetFocus(hObj);
}
}
break;
case WM_TOUCH_CHILD:
/* A descendent (child) has been
touched or released.
If it has been touched, we need to get to
top.
*/
{
const WM_MESSAGE * pMsgOrg;
const
GUI_PID_STATE * pState;
pMsgOrg = (const
WM_MESSAGE*)pMsg->Data.p; /* The original touch message */
pState = (const GUI_PID_STATE*)pMsgOrg->Data.p;
if (pState)
{ /* Message may not have a valid pointer (moved out) ! */
if (pState->Pressed) {
WM_BringToTop(hObj);
return 0; /* Message handled -> Return */
}
}
}
break;
case WM_SET_ID:
pWidget->Id = pMsg->Data.v;
return 0; /*
Message handled -> Return */
case WM_SET_FOCUS:
if
(pMsg->Data.v == 1) {
WIDGET_SetState(hObj, pWidget->State |
WIDGET_STATE_FOCUS);
Notification = WM_NOTIFICATION_GOT_FOCUS;
} else {
WIDGET_SetState(hObj, pWidget->State &
~WIDGET_STATE_FOCUS);
Notification =
WM_NOTIFICATION_LOST_FOCUS;
}
WM_NotifyParent(hObj,
Notification);
pMsg->Data.v = 0; /* Focus change accepted
*/
return 0;
case WM_GET_ACCEPT_FOCUS:
pMsg->Data.v =
(pWidget->State & WIDGET_STATE_FOCUSSABLE) ? 1 : 0; /* Can
handle focus */
return 0; /* Message handled
*/
case WM_GET_INSIDE_RECT:
WIDGET__GetInsideRect(pWidget,
(GUI_RECT*)pMsg->Data.p);
return 0; /*
Message handled */
}
return 1; /* Message
NOT handled
*/
}
證實(shí)了我的猜測,按鍵的父類Widget實(shí)現(xiàn)了對(duì)這樣的消息的攔截
注意這一句:
WIDGET* pWidget = WIDGET_H2P(hObj);
其實(shí)等于做了兩件事,首先獲得指針,然后類型轉(zhuǎn)換
記得C++中的
static_castpWidget;么?uCGUI中利用結(jié)構(gòu)體的巧妙安排使得類型轉(zhuǎn)換不需要指針偏移。
對(duì)于GET_ID的要求:
case WM_SET_ID:
pWidget->Id = pMsg->Data.v;
return
0; /* Message handled -> Return
*/
完成了取得Id的工作。
好了,我們獲得了消息來源的Id,是用WM__hWinFocus句柄取得的,別忘了:P
case
WM_KEY:
switch(((WM_KEY_INFO*)pMsg->Data.p)->Key),還記得WM_KEY_INFO和WM_MESSAGE么?
{
case
GUI_KEY_ENTER:
switch(WM_GetId(WM__hWinFocus))
{
case
GUI_ID_BUTTON0:
//這里就是在BUTTON0上按回車應(yīng)該做的事情(按下或釋放)
break;
case
GUI_ID_BUTTON1:
break;
}
}
現(xiàn)在我們已經(jīng)知道了怎么在自己定義的回調(diào)函數(shù)中接受對(duì)話框中某個(gè)按鍵按下的消息了,但屏幕上應(yīng)該怎么反應(yīng)呢?
應(yīng)該有個(gè)動(dòng)態(tài)的按下、釋放過程,然后再工作吧!
Button控件提供了一個(gè)函數(shù):
void
BUTTON_SetPressed(BUTTON_Handle hObj, int
State);
State參數(shù),0表示沒有被按下,1表示被按下
這個(gè)函數(shù)只是簡單地改變Button的狀態(tài)標(biāo)志(其中的Pressed位)么?當(dāng)然不是,它還將整個(gè)按鈕標(biāo)記為需要更新的Invalid,在下次GUI_EXEC()
中,將會(huì)重畫這個(gè)按鈕。
最終的代碼像這個(gè)樣子:
static
void DialogCallBack(WM_MESSAGE*
pMsg)
{
switch(pMsg->MsgId)
{
case WM_KEY:
switch(((WM_KEY_INFO*)pMsg->Data.p)->Key)
{
case
GUI_KEY_ENTER:
switch(WM_GetId(WM__hWinFocus))
{
case
GUI_ID_BUTTON0:
BUTTON_SetPressed(WM__hWinFocus,
((WM_KEY_INFO*)pMsg->Data.p)->PressedCnt);
break;
case
GUI_ID_BUTTON1:
BUTTON_SetPressed(WM__hWinFocus,
((WM_KEY_INFO*)pMsg->Data.p)->PressedCnt);
break;
}
break;
}
break;
default:
WM_DefaultProc(pMsg);
}
}