世上本无窗口,窗口只是人的眼睛和电脑屏幕及鼠标键盘相互操作后的视觉效果!
下面我们来看看我们之前讲过的代码:
class CDuiFrameWnd : public CWindowWnd, public INotifyUI { public: virtual LPCTSTR GetWindowClassName() const { return _T("DUIMainFrame"); } virtual void Notify(TNotifyUI& msg) { if (msg.sType == _T("click")) { if (msg.pSender->GetName() == _T("btnClick")) { ::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL); } } } virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lRes = 0; if( uMsg == WM_CREATE ) { CControlUI *pWnd = new CButtonUI; pWnd->SetName(_T("btnClick")); pWnd->SetText(_T("My First DUI")); // 设置文字 pWnd->SetBkColor(0xFF808080); // 设置背景色 m_PaintManager.Init(m_hWnd); //主窗口句柄 m_PaintManager.AttachDialog(pWnd); m_PaintManager.AddNotifier(this); return lRes; } if( m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes) ) { return lRes; } return __super::HandleMessage(uMsg, wParam, lParam); } protected: CPaintManagerUI m_PaintManager; };
先看红色标记部分
首先拦截WM_CREATE消息(如果你不知道是如何拦截的,请回去看领悟2),我们可以看到new出了一个CButtonUI,看名字知道这是一个按钮类,查duilib源码可知它从CControlUI派生,至于CControlUI是什么以后再讲,这里先要它是所有控件的基类.然后为这个按钮设置了控件ID::SetName(),设置显示名称::SetText(),并设置背景颜色
然后将主窗口句柄m_hWnd传递给了对象m_PaintManager,干嘛用的呢?当然是用来绘图的!
接着将按钮控件设置为m_PaintManger的控件树的根元素对应 --> m_PaintManger.AttachDialog(pWnd); 那么为什么要设置这么一个控件根元素?因为以后我们根据鼠标点击的位置来查找当前点击的是哪一个控件就是根据这个传递进来的根控件元素为起点开始遍历查找的!知道了吧?
再接着就是最重要的消息相关的了:m_PaintManager.AddNotifier(this); 有同学问了,就这么一句就可以实现消息路由了?我们看看AddNotifier()到底做了什么!
bool CPaintManagerUI::AddNotifier(INotifyUI* pNotifier) { ASSERT(m_aNotifiers.Find(pNotifier)<0); return m_aNotifiers.Add(pNotifier); }
private: CStdPtrArray m_aNotifiers; //Notify消息接收的指针队列 SendNotify()会遍历此链表给所有相关的消息接收者发送NOTIFY消息,自然会有纯虚函数Notify来接收并处理此消息 比如按钮点击等
看看m_aNotifiers在哪里引用过:
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/) { Msg.ptMouse = m_ptLastMousePos; Msg.dwTimestamp = ::GetTickCount(); if( m_bUsedVirtualWnd ) { Msg.sVirtualWnd = Msg.pSender->GetVirtualWnd(); } if( !bAsync ) { // Send to all listeners 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()是个纯虚函数,根据C++多态性,肯定会调用用户类的Notify();那么这个SendNofity()是在哪里被调用的呢?
void CPaintManagerUI::SendNotify(CControlUI* pControl, LPCTSTR pstrMessage, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/, bool bAsync /*= false*/) { TNotifyUI Msg; Msg.pSender = pControl; Msg.sType = pstrMessage; Msg.wParam = wParam; Msg.lParam = lParam; SendNotify(Msg, bAsync); }
这是SendNotify()的重载函数,又是哪位在调用它呢?
void CButtonUI::DoEvent(TEventUI& 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; }
}
bool CButtonUI::Activate() { if( !CControlUI::Activate() ) return false; if( m_pManager != NULL ) m_pManager->SendNotify(this, DUI_MSGTYPE_CLICK); return true; }
我们可以看到m_pManager,这是什么变量,查看可知,它是我们在用户类声明的CPaintManager对象,它是如何到达CButtonUI类成为它的成员变量的呢?
protected: CPaintManagerUI* m_pManager; //此变量保存的就是主窗口的那个m_PaintManager
上面代码解释了m_pManager,这是CControlUI的成员,而CButtonUI派生自CControlUI,所以该变量被CButtonUI所拥有,不足为怪!至于它是在哪里被赋值的,可以看下duilib源码,这里不再贴代码了!
这里就引出来一个问题,就是鼠标左键点击是如何到达CButtonUI的呢?????这是一个比较大的疑问! 请看代码:
case WM_LBUTTONUP: { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; m_ptLastMousePos = pt; if( m_pEventClick == NULL ) break; ReleaseCapture(); 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;
之前说过,CPaintManager成为所有消息的路由中心,由它决定消息该如何路由,上面的代码段中红色标记部分,封装了event,并传递给Event(),至于 m_pEventClick是如何来的,如何赋值的,请看case WM_LBUTTONDOWN分支,
case WM_LBUTTONDOWN: { // We alway set focus back to our app (this helps // when Win32 child windows are placed on the dialog // and we need to remove them on focus change). ::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(); 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); }
请看我用红色标记的部分,首先调用FindControl,根据pt查找所属控件:
CControlUI* CPaintManagerUI::FindControl(POINT pt) const { ASSERT(m_pRoot); return m_pRoot->FindControl(__FindControlFromPoint, &pt, UIFIND_VISIBLE | UIFIND_HITTEST | UIFIND_TOP_FIRST); }
其中
__FindControlFromPoint
是回调函数,
这个m_pRoot就是我们之前保存的CButtonUI的对象地址,调用的FindControl()当然是CControlUI的FindControl():
CControlUI* CControlUI::FindControl(FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags) { if( (uFlags & UIFIND_VISIBLE) != 0 && !IsVisible() ) return NULL; if( (uFlags & UIFIND_ENABLED) != 0 && !IsEnabled() ) return NULL; if( (uFlags & UIFIND_HITTEST) != 0 && (!m_bMouseEnabled || !::PtInRect(&m_rcItem, * static_cast<LPPOINT>(pData))) ) return NULL; return Proc(this, pData); // 根结点开始查找 }
红色标记部分的Proc()就是我们传递的回调函数地址__FindControlFromPoint(),为了证明我说的是对的下面给出调试信息:
可以清楚的看到Proc地址就是CPaintManagerUI::__FindControlFromPoint() 这是个静态函数,回调函数必须是静态或者全局;
逗乐个圈,讲了下回调函数调用过程!
目前我们的主窗口没有使用到CContainerUI,如果用到了,路径就不是这么走的了,根据继承性,会调用CContainerUI的FindControl(),不过最终还会回到基类CControlUI的FindControl(),呵呵
这样我们便得到了m_pEventClick指针指向的控件,接着调用pControl->Event(event):
void CControlUI::Event(TEventUI& event) { if( OnEvent(&event) ) DoEvent(event); }
virtual void Event(TEventUI& event); virtual void DoEvent(TEventUI& event);
这两个函数被声明为虚函数!
根据多态性,我想你知道我想说什么!呵呵!当然是派生类的DoEvent()啦!这样我们就可以处理鼠标点击左键的消息了,我们看看CButtonUI的DoEvent()做了什么?
代码有点长,做好准备哦!
void CButtonUI::DoEvent(TEventUI& event) { if( !IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND ) { if( m_pParent != NULL ) m_pParent->DoEvent(event); else CLabelUI::DoEvent(event); return; } if( event.Type == UIEVENT_SETFOCUS ) { Invalidate(); } if( event.Type == UIEVENT_KILLFOCUS ) { Invalidate(); } if( event.Type == UIEVENT_KEYDOWN ) { if (IsKeyboardEnabled()) { if( event.chKey == VK_SPACE || event.chKey == VK_RETURN ) { Activate(); return; } } } if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK ) { if( ::PtInRect(&m_rcItem, event.ptMouse) && IsEnabled() ) { m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED; Invalidate(); } return; } if( event.Type == UIEVENT_MOUSEMOVE ) { if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) { if( ::PtInRect(&m_rcItem, event.ptMouse) ) m_uButtonState |= UISTATE_PUSHED; else m_uButtonState &= ~UISTATE_PUSHED; Invalidate(); } return; } 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; } if( event.Type == UIEVENT_CONTEXTMENU ) { if( IsContextMenuUsed() ) { m_pManager->SendNotify(this, DUI_MSGTYPE_MENU, event.wParam, event.lParam); } return; } if( event.Type == UIEVENT_MOUSEENTER ) { if( IsEnabled() ) { m_uButtonState |= UISTATE_HOT; Invalidate(); } // return; } if( event.Type == UIEVENT_MOUSELEAVE ) { if( IsEnabled() ) { m_uButtonState &= ~UISTATE_HOT; Invalidate(); } // return; } if( event.Type == UIEVENT_SETCURSOR ) { ::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_HAND))); return; } CLabelUI::DoEvent(event); }
同MFC一样,最后也是返回给基类处理必要的操作或者其他没有被处理的消息;我用红色标记了一个Activate()函数:
bool CButtonUI::Activate() { if( !CControlUI::Activate() ) return false; if( m_pManager != NULL ) m_pManager->SendNotify(this, DUI_MSGTYPE_CLICK); return true; }
重点来了,就是这句红色的标记,就是它实现了消息发送给CPaintManager的对象来处理,而SendNotify()函数循环遍历m_aNotifiers消息接收者指针队列,调用Notify()虚函数,将消息发送给用户类CDuiFrameWnd类来告诉我们鼠标点击了按钮:
if( !bAsync ) { // Send to all listeners 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); } }
virtual void Notify(TNotifyUI& msg) { if (msg.sType == _T("click")) { if (msg.pSender->GetName() == _T("btnClick")) { ::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL); } } }
好了,控件的消息路由,就到这里吧!相信同学们认真看完应该没多大问题!