Duilib 窗口创建流程分析

1. 一般来说会自定义一个窗口类继承UIBase.h中定义的CWindowWnd类。

1 class CMainFrameUI:
2     public CBasicWnd,public INotifyUI,public IDialogBuilderCallback
3 {
4     ...
5 };

CWindowWnd类定义了一些接口,调用Windows对话框相关的API来创建显示窗口。
如:

1   CMainFrameUI * pMainframeUI = new CMainFrameUI();
2   if( pMainframeUI == NULL ) return 0;
3   pMainframeUI->Create(NULL, _T("Title"), /*WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_VISIBLE */UI_WNDSTYLE_FRAME , 0, 0, 0, 0);
4   ::ShowWindow(*pMainframeUI, SW_SHOW);
5   CPaintManagerUI::MessageLoop(); 

2. 这里的pMainFrameUI->Create会调用Windows的API ::RegisterClass 注册窗口 然后 ::CreateWindowEx 来创建一个窗口。

1 HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
2 {
3     if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
4     if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
5     m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
6     ASSERT(m_hWnd!=NULL);
7     return m_hWnd;
8 }

注册的时候会定义一个回调 CWindowWnd::__WndProc;

 1 bool CWindowWnd::RegisterWindowClass()
 2 {
 3     WNDCLASS wc = { 0 };
 4     wc.style = GetClassStyle();
 5     wc.cbClsExtra = 0;
 6     wc.cbWndExtra = 0;
 7     wc.hIcon = NULL;
 8     wc.lpfnWndProc = CWindowWnd::__WndProc;
 9     wc.hInstance = CPaintManagerUI::GetInstance();
10     wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
11     wc.hbrBackground = NULL;
12     wc.lpszMenuName  = NULL;
13     wc.lpszClassName = GetWindowClassName();
14     ATOM ret = ::RegisterClass(&wc);
15     ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
16     return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
17 }

回调函数:

 1 LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 2 {
 3     CWindowWnd* pThis = NULL;
 4     if( uMsg == WM_NCCREATE ) {
 5         LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
 6         pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
 7         pThis->m_hWnd = hWnd;
 8         ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
 9     } 
10     else {
11         pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
12         if( uMsg == WM_NCDESTROY && pThis != NULL ) {
13             LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
14             ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
15             if( pThis->m_bSubclassed ) pThis->Unsubclass();
16             pThis->m_hWnd = NULL;
17             pThis->OnFinalMessage(hWnd);
18             return lRes;
19         }
20     }
21     if( pThis != NULL ) {
22         return pThis->HandleMessage(uMsg, wParam, lParam);
23     } 
24     else {
25         return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
26     }
27 }

主要会调用:pThis->HandleMessage(uMsg, wParam, lParam);

 1 LRESULT CBasicWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
 2 {
 3     LRESULT lRes = 0;
 4     BOOL bHandled = TRUE;
 5 
 6     switch( uMsg ) {
 7     case WM_CREATE:        lRes = OnCreate(uMsg, wParam, lParam, bHandled); break;
 8     case WM_CLOSE:         lRes = OnClose(uMsg, wParam, lParam, bHandled); break;
 9     case WM_DESTROY:       lRes = OnDestroy(uMsg, wParam, lParam, bHandled); break;
10     case WM_NCACTIVATE:    lRes = OnNcActivate(uMsg, wParam, lParam, bHandled); break;
11     case WM_NCCALCSIZE:    lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled); break;
12     case WM_NCPAINT:       lRes = OnNcPaint(uMsg, wParam, lParam, bHandled); break;
13     case WM_NCHITTEST:     lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled); break;
14     case WM_SIZE:          lRes = OnSize(uMsg, wParam, lParam, bHandled); break;
15     case WM_GETMINMAXINFO: lRes = OnGetMinMaxInfo(uMsg, wParam, lParam, bHandled); break;
16      case WM_SYSCOMMAND:    lRes = OnSysCommand(uMsg, wParam, lParam, bHandled); break;
17     default:
18         bHandled = FALSE;
19     }
20 
21     if( bHandled ) return lRes;
22     if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes;
23 
24     return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
25 }

在这里处理 WM_CREATE 等消息

未处理的消息则 在这里处理  if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes;

bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes) 

这个消息处理函数比较复杂,下次再单独拿出来分析分析。

创建窗口时会收到 WM_CREATE消息:

 1 LRESULT CBasicWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
 2 {
 3     LONG styleValue = ::GetWindowLong(*this, GWL_STYLE);
 4     styleValue &= ~WS_CAPTION;
 5     styleValue &= ~WS_THICKFRAME;
 6     ::SetWindowLong(*this, GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
 7     m_WndShadow.Create(m_hWnd);
 8     OnSetShadowSize();
 9     m_WndShadow.SetPosition(0, 0);
10     Init();
11     return 0;
12 }

这里会进入 Init(); 进行初始化操作,然后就到了我们比较熟悉的流程。

3. ::ShowWindow(*pMainframeUI, SW_SHOW); 这里的*pMainframeUI 应该访问的是通过::CreateWindowEx返回的HWND句柄。
在CWindowWnd类中定义的变量类型,第一个是m_hWnd。

估计pMainframeUI指向的第一个数据就是 m_hWnd, 所以::ShowWindow 参数可以 *pMainframeUI 来访问。

 1 class DUILIB_API CWindowWnd
 2 {
 3 public:
 4     CWindowWnd();
 5 
 6     HWND GetHWND() const;
 7     operator HWND() const;
 8 
 9     bool RegisterWindowClass();
10     bool RegisterSuperclass();
11 
12     HWND Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, const RECT rc, HMENU hMenu = NULL);
13     HWND Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x = CW_USEDEFAULT, int y = CW_USEDEFAULT, int cx = CW_USEDEFAULT, int cy = CW_USEDEFAULT, HMENU hMenu = NULL);
14     HWND CreateDuiWindow(HWND hwndParent, LPCTSTR pstrWindowName,DWORD dwStyle =0, DWORD dwExStyle =0);
15     HWND Subclass(HWND hWnd);
16     void Unsubclass();
17     void ShowWindow(bool bShow = true, bool bTakeFocus = true);
18     UINT ShowModal();
19     void Close(UINT nRet = IDOK);
20     void CenterWindow();    // 居中,支持扩展屏幕
21     void SetIcon(UINT nRes);
22 
23     LRESULT SendMessage(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0L);
24     LRESULT PostMessage(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0L);
25     void ResizeClient(int cx = -1, int cy = -1);
26 
27 protected:
28     virtual LPCTSTR GetWindowClassName() const = 0;
29     virtual LPCTSTR GetSuperClassName() const;
30     virtual UINT GetClassStyle() const;
31 
32     virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
33     virtual void OnFinalMessage(HWND hWnd);
34 
35     static LRESULT CALLBACK __WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
36     static LRESULT CALLBACK __ControlProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
37 
38 protected:
39     HWND m_hWnd;
40     WNDPROC m_OldWndProc;
41     bool m_bSubclassed;
42 };

4. CPaintManagerUI::MessageLoop();是消息循环函数

 1 void CPaintManagerUI::MessageLoop()
 2 {
 3     MSG msg = { 0 };
 4     while( ::GetMessage(&msg, NULL, 0, 0) ) {
 5         if( !CPaintManagerUI::TranslateMessage(&msg) ) {
 6             ::TranslateMessage(&msg);
 7             ::DispatchMessage(&msg);
 8             }
 9         }
10     }
11 }

这是一个while循环,从线程消息队列获取消息,然后处理,不过微软不太建议这样写,因为GetMessage返回值可能是0,-1,非0;

如果收到WM_QUIT消息,就会返回0退出,表示程序退出。

建议这样写,自已编译源码的时候可以改下,不过问题应该也不大的。

 1 BOOL bRet;
 2 
 3 while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0)
 4 { 
 5     if (bRet == -1)
 6     {
 7         // handle the error and possibly exit
 8     }
 9     else
10     {
11         TranslateMessage(&msg); 
12         DispatchMessage(&msg); 
13     }
14 }

下面看看 CPaintManagerUI::TranslateMessage 是如何处理消息的

 1 bool CPaintManagerUI::TranslateMessage(const LPMSG pMsg)
 2 {
 3     // Pretranslate Message takes care of system-wide messages, such as
 4     // tabbing and shortcut key-combos. We'll look for all messages for
 5     // each window and any child control attached.
 6     UINT uStyle = GetWindowStyle(pMsg->hwnd);
 7     UINT uChildRes = uStyle & WS_CHILD;    
 8     LRESULT lRes = 0;
 9     if (uChildRes != 0)
10     {
11         HWND hWndParent = ::GetParent(pMsg->hwnd);
12         //code by redrain 2014.12.3,解决edit和webbrowser按tab无法切换焦点的bug
13         //        for( int i = 0; i < m_aPreMessages.GetSize(); i++ ) 
14         for( int i = m_aPreMessages.GetSize() - 1; i >= 0 ; --i ) 
15         {
16             CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);        
17             HWND hTempParent = hWndParent;
18             while(hTempParent)
19             {
20 
21                 if(pMsg->hwnd == pT->GetPaintWindow() || hTempParent == pT->GetPaintWindow())
22                 {
23                     if (pT->TranslateAccelerator(pMsg))
24                         return true;
25 
26                     pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes);
27                     //                     if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) ) 
28                     //                         return true;
29                     // 
30                     //                     return false;  
31                 }
32                 hTempParent = GetParent(hTempParent);
33             }
34 
35         }
36     }
37     else
38     {
39         for( int i = 0; i < m_aPreMessages.GetSize(); i++ ) 
40         {
41             int size = m_aPreMessages.GetSize();
42             CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);
43             if(pMsg->hwnd == pT->GetPaintWindow())
44             {
45                 if (pT->TranslateAccelerator(pMsg))
46                     return true;
47 
48                 if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) ) 
49                     return true;
50 
51                 return false;
52             }
53         }
54     }
55     return false;
56 }

处理子窗口情况或者处理当前窗口,先处理快捷键操作,将快捷键转换成消息发送出去。

如果不是快捷键,则找到对应的窗口调用 PreMessageHandler处理

m_aPreMessages 是创建的所有 CPaintManagerUI 实例(相当于遍历所有窗口)

 1 bool CPaintManagerUI::PreMessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& /*lRes*/)
 2 {
 3     for( int i = 0; i < m_aPreMessageFilters.GetSize(); i++ ) 
 4     {
 5         bool bHandled = false;
 6         LRESULT lResult = static_cast<IMessageFilterUI*>(m_aPreMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);
 7         if( bHandled ) {
 8             return true;
 9         }
10     }
11     switch( uMsg ) {
12     case WM_KEYDOWN:
13         {
14            // Tabbing between controls
15            if( wParam == VK_TAB ) {
16                if( m_pFocus && m_pFocus->IsVisible() && m_pFocus->IsEnabled() && _tcsstr(m_pFocus->GetClass(), DUI_CTR_RICHEDIT) != NULL ) {
17                    if( static_cast<CRichEditUI*>(m_pFocus)->IsWantTab() ) return false;
18                }
19                SetNextTabControl(::GetKeyState(VK_SHIFT) >= 0);
20                return true;
21            }
22         }
23         break;
24     case WM_SYSCHAR:
25         {
26            if( m_pRoot == NULL ) return false;
27            // Handle ALT-shortcut key-combinations
28            FINDSHORTCUT fs = { 0 };
29            fs.ch = toupper((int)wParam);
30            CControlUI* pControl = m_pRoot->FindControl(__FindControlFromShortcut, &fs, UIFIND_ENABLED | UIFIND_ME_FIRST | UIFIND_TOP_FIRST);
31            if( pControl != NULL ) {
32                pControl->SetFocus();
33                pControl->Activate();
34                return true;
35            }
36         }
37         break;
38     case WM_SYSKEYDOWN:
39         {
40            if( m_pFocus != NULL ) {
41                TEventUI event = { 0 };
42                event.Type = UIEVENT_SYSKEY;
43                event.pSender = m_pFocus;
44                event.chKey = (TCHAR)wParam;
45                event.ptMouse = m_ptLastMousePos;
46                event.wKeyState = MapKeyState();
47                event.dwTimestamp = ::GetTickCount();
48                m_pFocus->Event(event);
49            }
50         }
51         break;
52     }
53     return false;
54 }

m_aPreMessageFilters 是自定义的消息处理

实现接口 virtual LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled) = 0;

优先调用自定义的消息处理函数进行处理,然后再处理一下按键值消息

m_aPreMessageFilters 需要手动调用 bool CPaintManagerUI::AddPreMessageFilter(IMessageFilterUI* pFilter) 添加

否则 m_aPreMessageFilters 的 size 是为0的

原文地址:https://www.cnblogs.com/george-cw/p/14286362.html