DuiLib(一)——窗口及消息
最近看了下开源界面库duilib的代码,写几篇相关的文章。网上已经有好多相关的文章了,我这里只是记录自己的学习过程,写到哪里算哪里,权当自娱自乐。
duilib是一轻量级的direcui界面库,所谓directui是指在一真实的窗口之上画出各种控件。所以先从界面库的窗口及消息入手比较好,可以抓住树根,再顺着往上分析。
duilib将窗口封装成类CWindowWnd,创建窗口之前要先注册窗口:
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 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 LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 2 { 3 CWindowWnd* pThis = NULL; 4 if( uMsg == WM_NCCREATE ) {//before WM_CREATE 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 ) {//after WM_DESTROY 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 //消息处理 23 return pThis->HandleMessage(uMsg, wParam, lParam); 24 } 25 else { 26 return ::DefWindowProc(hWnd, uMsg, wParam, lParam); 27 } 28 }
窗口函数__WndProc:
- 处理了WM_NCCREATE消息(WM_CREATE之前发送)和WM_NCDESTROY(WM_DESTROY之后发送)。前者保存了CWindowWnd对象指针,后者获取该指针,并调用虚函数OnFinalMessage,给用户一个最后清理的机会。
- 调用虚函数HandleMessage,处理其他消息。
消息循环在哪?不在类CWindowWnd中,在CPaintManagerUI里!
void CPaintManagerUI::MessageLoop() { MSG msg = { 0 }; while( ::GetMessage(&msg, NULL, 0, 0) ) { if( !CPaintManagerUI::TranslateMessage(&msg) ) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } } }
一个简单的示例:
class CFrameWindowWnd : public CWindowWnd, public INotifyUI { public: CFrameWindowWnd() { }; LPCTSTR GetWindowClassName() const { return _T("FrameWnd"); }; UINT GetClassStyle() const { return UI_CLASSSTYLE_FRAME | CS_DBLCLKS; }; void OnFinalMessage(HWND /*hWnd*/) { delete this; }; void Notify(TNotifyUI& msg) { if( msg.sType == _T("windowinit") ) { } else if( msg.sType == _T("click") ) { } } //消息处理:窗口函数__WndProc ---> HandleMessage LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { if( uMsg == WM_CREATE ) { m_pm.Init(m_hWnd); //根据XML创建控件 CDialogBuilder builder; CControlUI* pRoot = builder.Create(_T("HelloWorld.xml"), (UINT)0, NULL, &m_pm); ASSERT(pRoot && "Failed to parse XML"); m_pm.AttachDialog(pRoot); m_pm.AddNotifier(this); return 0; } else if( uMsg == WM_DESTROY ) { ::PostQuitMessage(0L); } else if( uMsg == WM_ERASEBKGND ) { return 1; } //消息处理:CPaintManagerUI::MessageHandler LRESULT lRes = 0; if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes; return CWindowWnd::HandleMessage(uMsg, wParam, lParam); } public: CPaintManagerUI m_pm; }; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow) { CPaintManagerUI::SetInstance(hInstance); CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T("skin\\HelloWorldRes")); //COM HRESULT Hr = ::CoInitialize(NULL); if( FAILED(Hr) ) return 0; CFrameWindowWnd* pFrame = new CFrameWindowWnd(); if( pFrame == NULL ) return 0; //注册窗口类、创建窗口 pFrame->Create(NULL, _T("HelloWorld"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); pFrame->CenterWindow(); pFrame->ShowWindow(true); //消息循环 CPaintManagerUI::MessageLoop(); //COM ::CoUninitialize(); return 0; }
这个例子可以看到整个程序框架,注册、创建窗口、消息循环、消息处理等。
关于窗口和消息,先写这么多。下一篇写控件创建。