DuiLib(三)——控件消息
上一篇讲了控件创建,这篇说说控件消息。directui的中心思想是在真实窗口之上画出所有控件,那这些控件是如何获取各自消息的?
通过第一篇的示例可以看到窗口消息的传递过程:
- CWindowWnd::__WndProc
- CWindowWnd::HandleMessage(CFrameWindowWnd类覆盖此函数)
- CPaintManagerUI::MessageHandler
消息最终传递到CPaintManagerUI::MessageHandler中,以WM_LBUTTONDOWN消息为例,看看消息如何分发到控件
。。。
1 case WM_LBUTTONDOWN: 2 { 3 // We alway set focus back to our app (this helps 4 // when Win32 child windows are placed on the dialog 5 // and we need to remove them on focus change). 6 ::SetFocus(m_hWndPaint); 7 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; 8 m_ptLastMousePos = pt; 9 CControlUI* pControl = FindControl(pt);//查找控件 10 if( pControl == NULL ) break; 11 if( pControl->GetManager() != this ) break; 12 m_pEventClick = pControl; 13 pControl->SetFocus(); 14 SetCapture(); 15 TEventUI event = { 0 }; 16 event.Type = UIEVENT_BUTTONDOWN; 17 event.pSender = pControl; 18 event.wParam = wParam; 19 event.lParam = lParam; 20 event.ptMouse = pt; 21 event.wKeyState = (WORD)wParam; 22 event.dwTimestamp = ::GetTickCount(); 23 pControl->Event(event);//事件--->控件 24 } 25 break;
。。。
可以清楚地看到,分发消息分为两步:
- 查找控件:CPaintManagerUI::FindControl
- 调用控件接口:CControlUI::Event
现在看看控件查找是怎么回事:
1 CControlUI* CPaintManagerUI::FindControl(POINT pt) const 2 { 3 ASSERT(m_pRoot); 4 return m_pRoot->FindControl(__FindControlFromPoint, &pt, UIFIND_VISIBLE | UIFIND_HITTEST | UIFIND_TOP_FIRST); 5 }
m_pRoot看名字应该是根控件了,在第二篇的控件创建过程中CDialogBuilder::Create返回了根控件,并且通过CPaintManagerUI::AttachDialog将根控件指针保存在了m_pRoot中。看看xml文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <Window mininfo="400,240" size="400,240" alpha="250"> 3 <Font name="微软雅黑" size="18" bold="true" default="true"/> 4 <VerticalLayout inset="10,6,10,6" bkimage="back.jpg"> 5 <Label name="label" float="true" pos="10,10,100,30" bkcolor="#FF00FFFF" textcolor="#FFFFFFFF" text="Label"/> 6 </VerticalLayout> 7 </Window>
根控件为VerticalLayout,是CContainerUI控件容器子类。而CContainerUI又是CControlUI子类。创建控件时,所有的子控件都添加到了根控件容器中,查找控件自然通过根控件容器实现。
1 CControlUI* CContainerUI::FindControl(FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags) 2 { 3 。。。 4 5 if( (uFlags & UIFIND_TOP_FIRST) != 0 ) { 6 for( int it = m_items.GetSize() - 1; it >= 0; it-- ) { 7 CControlUI* pControl = static_cast<CControlUI*>(m_items[it])->FindControl(Proc, pData, uFlags); 8 if( pControl != NULL ) { 9 if( (uFlags & UIFIND_HITTEST) != 0 && !pControl->IsFloat() && !::PtInRect(&rc, *(static_cast<LPPOINT>(pData))) ) 10 continue; 11 else 12 return pControl; 13 } 14 } 15 } 16 else { 17 for( int it = 0; it < m_items.GetSize(); it++ ) { 18 CControlUI* pControl = static_cast<CControlUI*>(m_items[it])->FindControl(Proc, pData, uFlags); 19 if( pControl != NULL ) { 20 if( (uFlags & UIFIND_HITTEST) != 0 && !pControl->IsFloat() && !::PtInRect(&rc, *(static_cast<LPPOINT>(pData))) ) 21 continue; 22 else 23 return pControl; 24 } 25 } 26 } 27 28 。。。 29 }
容器CContainerUI的成员m_items中保存了所有子控件指针,通过遍历子控件(调用CControlUI::FindControl)查找符合条件的子控件,还可以根据标识uFlags进行不同的查找方式。
查找到子控件后,再看看控件的事件接口CControlUI::Event。很明显这是一个虚函数,通过多态,各子控件可以实现自己的事件处理函数。比如控件CButtonUI可以处理UIEVENT_KEYDOWN、UIEVENT_BUTTONUP事件,更新按钮状态。
至此,控件消息分发介绍完了。那如何添加控件事件处理?答案是通过INotifyUI接口。在第一篇的示例中,框架类CFrameWindowWnd不光继承了窗口类CWindowWnd,也实现了INotifyUI接口。INotifyUI很简单
1 class INotifyUI 2 { 3 public: 4 virtual void Notify(TNotifyUI& msg) = 0; 5 };
接口参数为结构体TNotifyUI
1 typedef struct tagTNotifyUI 2 { 3 CStdString sType;//事件类型 4 CControlUI* pSender;//控件 5 DWORD dwTimestamp; 6 POINT ptMouse; 7 WPARAM wParam; 8 LPARAM lParam; 9 } TNotifyUI;
框架类只要实现这个接口,并注册(CPaintManagerUI::AddNotifier),就可以监听控件事件了。这是典型的观察者模式。再看看第一篇给出的示例代码。
1 class CFrameWindowWnd : public CWindowWnd, public INotifyUI 2 { 3 public: 4 CFrameWindowWnd() { }; 5 LPCTSTR GetWindowClassName() const { return _T("FrameWnd"); }; 6 UINT GetClassStyle() const { return UI_CLASSSTYLE_FRAME | CS_DBLCLKS; }; 7 void OnFinalMessage(HWND /*hWnd*/) { delete this; }; 8 9 void Notify(TNotifyUI& msg) 10 { 11 if( msg.sType == _T("windowinit") ) { 12 } 13 else if( msg.sType == _T("click") ) { 14 } 15 } 16 17 //消息处理:窗口函数__WndProc ---> HandleMessage 18 LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) 19 { 20 if( uMsg == WM_CREATE ) { 21 m_pm.Init(m_hWnd); 22 //根据XML创建控件 23 CDialogBuilder builder; 24 CControlUI* pRoot = builder.Create(_T("HelloWorld.xml"), (UINT)0, NULL, &m_pm); 25 ASSERT(pRoot && "Failed to parse XML"); 26 m_pm.AttachDialog(pRoot); 27 m_pm.AddNotifier(this); 28 return 0; 29 } 30 else if( uMsg == WM_DESTROY ) { 31 ::PostQuitMessage(0L); 32 } 33 else if( uMsg == WM_ERASEBKGND ) { 34 return 1; 35 } 36 37 //消息处理:CPaintManagerUI::MessageHandler 38 LRESULT lRes = 0; 39 if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes; 40 return CWindowWnd::HandleMessage(uMsg, wParam, lParam); 41 } 42 43 public: 44 CPaintManagerUI m_pm; 45 };
关于控件消息基本就这些了,有些地方没有细究,先了解大体流程比较重要。