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 };

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

你可能感兴趣的:(lib)