有所追求
分类:
2008-01-30 22:21:10
Edited by QiuYue
原发于 @2007-7-4
转帖请注明出处
【献给初学者,若您是高手请不吝赐教,甚为感激!】
接触uCGUI已经快一周了,贴点儿心得,贻笑大方……请大家指正
把Msg的结构体写在最前面:
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;
首先跟踪一个按键发送函数
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;
}
注意,消息发送到了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);
}
}
消息发到了哪儿?yeah,就是我们直观看到的当前焦点--button
调用其回调函数,一个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)由句柄获得对象
(2)判断消息类型,本次发送消息为一个按键类型
(1-2)局部变量,获得按下的key和状态
这个函数有点儿令人匪夷所思,如果按下空格,则执行_ButtonPressed(hObj, pObj);
如果是释放空格,则执行_ButtonReleased(hObj, pObj, WM_NOTIFICATION_RELEASED);
这两个函数是做什么的?
这些都是private函数,对外不提供接口
ucgui利用c的特性,很好的模拟了封装、继承等等面向对象特性
这里的private实现很好理解
类实现代码中实现的行为不在其头(.h)文件中提供声明
其它通过包含头文件使用这个类的函数由于没有某些函数的声明故不能调用
就完成了private特性
首先是_ButtonPressed(BUTTON_Handle hObj, BUTTON_Obj* pObj)
这个函数比较容易理解,更改外观的同时将消息上泵,通知自己的父窗体,自己的模样被更新了
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)首先将这个Button对象的状态设置成按下的
(2)看看这个Button对象的状态是不是可见的(若在C++中,可以实现pObj->Status,不需要利用Widget.Win标签但这里我们可以清楚地看出类中组合关系)
(3)如果是可见的,就通知父窗体,触发一个单击事件。
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。(注意,Framewin和Client对象都有回调函数,但Button的父窗体是Client,所以执行的不是Framewin而是Client的回调函数.)如此,一个按下消息发给了Client,如果Client中不进行拦截,则将传给用户定义的
接口回调函数。
再让我们看看释放函数:
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);
}
}
后面一个if是陌生的
if (Notification == WM_NOTIFICATION_RELEASED) {
GUI_DEBUG_LOG("BUTTON: Hit\n");
GUI_StoreKey(pObj->Widget.Id);
}
此处我不是很理解,当按下-释放过程结束时,就证明一个按键工作结束,将这个按键的接受者的Id放到系统的Key队列中去?(后经试验发现,目的大概为了保护按键信息,以便后期处理。)
综上我们已经可以分析一个按键消息通过系统的路径(以Button为例)
按键--》找到focus窗体,将消息发送给它--》在这个窗体的回调函数中,如果定义了这个按键值,则将其拦截并工作
(以按键的回调函数为例,如果按键了,则将这个消息以NOTIFYPARENT的形式向上泵,知道用户定义的接口回调函数)
如果button回调函数没有拦截这个按键值呢?
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;
}
注意,这里对于按键,直接向上泵(parent)
case WM_KEY:
WM_SendToParent(hWin, pMsg);
return; /* Message handled */
这儿的句柄hWin还是Button,向上泵给Client,再泵给用户定义回调函数,最终在那里结束。
此处有一点提示,因为用户回调函数中通常也会用到WM_DefaultProc这个过程处理不拦截的键,那么会不会产生死循环呢?
当然不会,因为在我们做WM_SendToParent函数的时候,MSG的目标句柄已经变成了Client窗体,而在Client中直接泵给用户定义接口时,
而在Client的回调函数中,又巧妙地将MSG的目标句柄编程了FRAMEWIN,这样在我们定义的接口回调函数中,我们收到的是一个发送到FRAMEWIN的消息。这里聪明地使Client
窗体这一层对用户透明了。
看到这里,再回头看一个预编译标志#if 0
我们可以确定的是,在Button控件中压根不拦截WM_KEY消息,而是直接提供给DefaultProc中,通过DefaultProc泵给Client,随后再直接提供给
用户定义的接口回调函数。
知道了这样的一个消息传递过程,我们就可以放心大胆的在自己定义的接口回调函数中定义对各种各样的键进行处理的过程了
case WM_KEY就可以拦截到传递给Button的所有按键,但怎么判断传来的值是来自Button呢?我们回过头去看那个被传递的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,我们无法从消息中判断信息来源了!?当然可以,消息是发送到WM__hWinFocus的,而在消息传递的过程中没有改变这个
全局的指针,我们可以大胆的把WM__hWinFocus的句柄拿来操作。但得到的只是一个句柄,如何知道它是Button?还是EditBox还是(*&……&?
uCGUI提供了一个由句柄得到Id的函数
int WM_GetId (WM_HWIN hWin);
反过来,从Id也可以得到控件的句柄,用的是函数
其中第一个参数是控件属于的窗体句柄,
WM_HWIN WM_GetDialogItem (WM_HWIN hWin, int Id);
是在内存管理上做的文章
我认为,这里应该是模仿windows编程中的句柄-id-对象互相调用的几个函数
这个函数中使用了递归,去查询所有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;
}
发送一个消息,像句柄索要它的Id
在观察Button的callback时,发现没有现式地拦截WM_GET_ID这一消息,但根据常识,这样一个标准接口不应该在最终的实体化的类中实现,
应该由它的父类或祖父类实现,仔细观察,注意到这一句:
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 */
}
证实了我的猜测,按键的父类Widget实现了对这样的消息的拦截
注意这一句:
WIDGET* pWidget = WIDGET_H2P(hObj);
其实等于做了两件事,首先获得指针,然后类型转换
记得C++中的 static_cast
对于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上按回车应该做的事情(按下或释放)
break;
case GUI_ID_BUTTON1:
break;
}
}
现在我们已经知道了怎么在自己定义的回调函数中接受对话框中某个按键按下的消息了,但屏幕上应该怎么反应呢?
应该有个动态的按下、释放过程,然后再工作吧!
Button控件提供了一个函数:
void BUTTON_SetPressed(BUTTON_Handle hObj, int State);
State参数,0表示没有被按下,1表示被按下
这个函数只是简单地改变Button的状态标志(其中的Pressed位)么?当然不是,它还将整个按钮标记为需要更新的Invalid,在下次GUI_EXEC()
中,将会重画这个按钮。
最终的代码像这个样子:
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);
}
}
有点儿乱……