1.简介
本节研究下当一个 Button 按钮被点击时,消息是如何一步步传输的,并最终被主窗口中的 Notify() 函数处理掉。 通过此,可以清晰的了解 Notify 消息的产生过程及传递过程。
本节基于需要了解的一点是:在自定义窗口中的,如果 HandleMessage() 不对消息进行处理,则最终会走向 CPaintManagerUI::MessageHandler() 函数中,此函数对多种系统消息进行了处理。
2.鼠标左键按下
当鼠标左键按下时,系统会向窗口发送 WM_LBUTTONDOWN 消息,经过一系列传递后,WM_LBUTTONDOWN 到达 CPaintManagerUI::MessageHandler() 函数中。CPaintManagerUI中有成员变量 m_pEventClick 用于记录点击事件由哪个控件处理,在此处进行赋值,方便在鼠标左键抬起时使用。
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
{
//异步消息处理......
//消息过滤......
switch( uMsg )
{
//此处省略了其他各种case处理
case WM_LBUTTONDOWN: //系统消息:左键按下
{
//窗口获得焦点
::SetFocus(m_hWndPaint);
//根据鼠标位置判断是哪个控件被左键按下
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
CControlUI* pControl = FindControl(pt);
if( pControl == NULL ) break;
if( pControl->GetManager() != this ) break;
//将焦点设置在控件上(当然会有关于焦点的一系列消息发生)
m_pEventClick = pControl;
pControl->SetFocus();
//捕获鼠标
SetCapture();
//初始化event事件
TEventUI event = { 0 };
event.Type = UIEVENT_BUTTONDOWN;
event.pSender = pControl;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
//处理按下事件:按钮变换背景颜色等
pControl->Event(event);
}
break;
default:
break;
}
//异步消息处理......
return false;
}
接着简介下 pControl->Event(event) 这句。由于 CButtonUI 未重载 Event() 函数,故调用父类 CControlUI 的,如下:
void CControlUI::Event(TEventUI& event)
{
if( OnEvent(&event) )
DoEvent(event);
}
CButtonUI 并没有给 OnEvent 设置代理(后续再介绍),故会返回true,接着会直接调用 DoEvent() 函数。DoEvent() 会对多种event事件进行处理,在这里仅将对 UIEVENT_BUTTONDOWN 的处理贴出来:
void CButtonUI::DoEvent(TEventUI& event)
{
//其他各种event处理
if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK )
{
if( ::PtInRect(&m_rcItem, event.ptMouse) && IsEnabled() )
{
m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED; //更改状态样式
Invalidate(); //刷新控件
}
return;
}
CLabelUI::DoEvent(event);
}
3.鼠标左键抬起
左键抬起的消息 WM_LBUTTONUP 的处理流程跟按下左键基本相同,bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes) 中对 WM_LBUTTONUP 消息的处理过程如下:
case WM_LBUTTONUP:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
if( m_pEventClick == NULL ) break;
//释放鼠标捕获
ReleaseCapture();
//初始化event事件
TEventUI event = { 0 };
event.Type = UIEVENT_BUTTONUP;
event.pSender = m_pEventClick;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
//处理松开鼠标事件
m_pEventClick->Event(event);
m_pEventClick = NULL;
}
break;
之后便会进入 CButtonUI::DoEvent, 如下:
void CButtonUI::DoEvent(TEventUI& event)
{
//其他各种event处理
if( event.Type == UIEVENT_BUTTONUP )
{
if( (m_uButtonState & UISTATE_CAPTURED) != 0 )
{
if( ::PtInRect(&m_rcItem, event.ptMouse) )
Activate();
m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
Invalidate();
}
return;
}
CLabelUI::DoEvent(event);
}
如果按下鼠标与松开鼠标的过程中没有移动鼠标,则鼠标位置仍在Button中,PtInRect()返回true,进入 Activate() 函数,如下:
bool CButtonUI::Activate()
{
if( !CControlUI::Activate() )
return false;
if( m_pManager != NULL )
m_pManager->SendNotify(this, DUI_MSGTYPE_CLICK);
return true;
}
4.进入 SendNotify 函数
从上面代码中可以看出,会执行 m_pManager->SendNotify(),而且是同步消息,消息会立即被执行。执行的过程简化如下:
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)
{
Msg.ptMouse = m_ptLastMousePos;
Msg.dwTimestamp = ::GetTickCount();
if( m_bUsedVirtualWnd )
{
Msg.sVirtualWnd = Msg.pSender->GetVirtualWnd();
}
if( Msg.pSender != NULL )
{
//控件本身处理此消息
if( Msg.pSender->OnNotify )
Msg.pSender->OnNotify(&Msg);
}
for( int i = 0; i < m_aNotifiers.GetSize(); i++ )
{
//同步消息,被立即执行
static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
}
}
故窗口的 Notify() 函数将会直接被执行。至于你要对按钮点击做出什么响应,在Notify()中实现就可以了。
5.结束
印证了一点,所有的notify消息都是通过 CPaintManagerUI::SendNotify() 产生的。