DuiLib(三)——控件消息

上一篇讲了控件创建,这篇说说控件消息。directui的中心思想是在真实窗口之上画出所有控件,那这些控件是如何获取各自消息的?

通过第一篇的示例可以看到窗口消息的传递过程:

  1. CWindowWnd::__WndProc
  2. CWindowWnd::HandleMessage(CFrameWindowWnd类覆盖此函数)
  3. 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;

。。。

可以清楚地看到,分发消息分为两步:

  1. 查找控件:CPaintManagerUI::FindControl
  2. 调用控件接口: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 };

关于控件消息基本就这些了,有些地方没有细究,先了解大体流程比较重要。

原文地址:https://www.cnblogs.com/dahai/p/3457026.html