duilib整体的操作流程
刚开始的时候设置
CPaintManagerUI::SetInstance(hInstance);
CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());
::CoInitialize(NULL);
这里设置的resourcepath是接下去图片资源的位置。
并初始化了COM库,并且在退出的时候要执行::CoUninitialize();
CMainWndDlg* pMainDlg = new CMainWndDlg();
pMainDlg->Create(NULL, _T("cxiaoln窗体"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
pMainDlg->CenterWindow();
pMainDlg->ShowModal();
接下去是Create函数,里面执行的是
HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu) { if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL; if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL; m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this); ASSERT(m_hWnd!=NULL); return m_hWnd; }
这里首先要判断的是否是SuperClassName,关于这个SuperClassName我们在CEditUI中已经说明了。如果是系统本身自带的控件则使用SuperClassName,如果自己创建的窗口则调用普通的GetWindowClassName
注册完成窗口之后,接下去就是创建窗口::CreateWindowEx
再接下去就是ShowModal,进行消息的循环。其实从ShowModal 我们发现它的消息循环跟CPaintManagerUI::MessageLoop();的消息循环是差不多的。消息都要先让CPaintManagerUI::TranslateMessage(&msg)进行处理,如果没有对应的控件进行处理才进入
::TranslateMessage(&msg);
::DispatchMessage(&msg);
进行消息处理。
为什么要这样子呢? 其实我们稍微分析一下就知道了,我们在创建窗口的时候创建的其实只是一个面板,然后具体的界面上面的按钮其实只是一些图片。或者颜色的绘制。具体这些界面上面的空间如何或者响应待会再讲。
先说这里的ShowModal则会开始调用消息。
按创建窗体一般的思路就传递WM_CREATE的消息,然后再传递WM_SIZE消息。pThis->HandleMessage(uMsg, wParam, lParam);
所以就会调用
首先创建窗口:OnCreate
LRESULT CMainWndDlg::OnCreate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { m_PaintManager.Init(m_hWnd); CDialogBuilder builder; CControlUI* pRoot = builder.Create(_T("DemoSkin.xml"), (UINT)0, NULL, &m_PaintManager); // DemoSkin.xml需要放到exe目录下 ASSERT(pRoot && "Failed to parse XML"); m_PaintManager.AttachDialog(pRoot); m_PaintManager.AddNotifier(this); // 添加控件等消息响应,这样消息就会传达到duilib的消息循环,我们可以在Notify函数里做消息处理 Init(); return 0; }
这里首先进行了m_PaintManager.Init(m_hWnd);先进行了初始化,
void CPaintManagerUI::Init(HWND hWnd) { ASSERT(::IsWindow(hWnd)); // Remember the window context we came from m_hWndPaint = hWnd; m_hDcPaint = ::GetDC(hWnd); // We'll want to filter messages globally too m_aPreMessages.Add(this); }
这里将m_PaintManager添加进了m_aPreMessages的数组。当在调用PreMessageHandler时,会先调用相关的有继承IMessageFilterUI的UI,但是我们发现这个数组是静态的,如果进行多线程的操作时候很有可能会出现多线程不安全的情况。所以多线程的时候得慎用。
在Init之后则会使用CDialogBuilder类进行了创建,builder.Create(xml),即解析xml函数,具体如何解析在上一章中已经说明了。
然后这里的AttachDialog,顾名思义就是将pRoot跟CPaintManager关联起来,其实主要是获取pRoot的各种参数(里面保存了xml文件解析的各种数据)。
接下去如果有继承INotify类,则这里可以添加AddNotifier(this),从而,当有事件发生并发送了SendNotify的时候,OnNotify函数则会收到相应的消息。具体发送的消息内容则根据每个UI控件。
===================================
那每个控件是如何响应的呢?
其实原理很简单,就是点击鼠标的时候,根据鼠标所在的位置当前的控件是什么FindControl(pt)来进行响应该操作对应的事件。具体可以看bool CPaintManagerUI::MessageHandler
如果出现重叠控件怎么办?
其实在最开始解析xml的时候已经给了答案了,它在将控件放进容器控件中时是按顺序存放的,当它在滚动台哦还有其他参数的方式进行查找还是没找到时候,则会进行如下方式的操作
if( (uFlags & UIFIND_TOP_FIRST) != 0 ) { for( int it = m_items.GetSize() - 1; it >= 0; it-- ) { CControlUI* pControl = static_cast<CControlUI*>(m_items[it])->FindControl(Proc, pData, uFlags); if( pControl != NULL ) { if( (uFlags & UIFIND_HITTEST) != 0 && !pControl->IsFloat() && !::PtInRect(&rc, *(static_cast<LPPOINT>(pData))) ) continue; else return pControl; } } }
是从后往前进行遍历查找,即使同一个位置有两个控件它也能很容易的找到最后放进去的那个(isfloat)