從GoogleCode上下載的duilib工程中附帶的一副總體設計圖(如下所示),可以先整體了解一下,有個初步的認識,對后續進一步深入了解學習會很有幫助。
通過設計圖有了一個初步認識后,接下來開始進一步深入學習了解,主要從以下幾個方面進行了解學習:
庫的組成;框架基本流程;元素創建機制;消息處理機制。
1. 庫的基本組成
1.1 工具庫

- UI相關:CPoint / CSize / CDuiRect
- 簡單容器:CStdPtrArray / CStdValArray / CStdString / CStdStringPtrMap
上面這些類看名字就基本能夠理解其具體的含義了,當然除了基本的基礎庫,還有一些和窗口使用相關的工具的封裝,如窗口工具:WindowImplBase,這個工具我們在這里不詳述,后面使用中會經常用到。
1.2 控件庫
控件庫在duilib的實現中被分為了兩塊:Core和Control:
- Core中包含的是所有控件公用的部分,里面主要是一些基類和繪制的封裝。
- Control中包含的就是各個不同的控件的行為了。
這當中尤其要注意控件基類CControlUI和容器基類CContainerUI,這是duilib核心類(如下圖所示)中是很重要的兩部分:
1.2.1. 控件基類:CControlUI
CControlUI在整個控件體系中非常重要,它是所有控件的基類,也是組成控件樹的基本元素,控件樹中所有的節點都是一個CControlUI。
它基本包括了所有控件公共的屬性,如:位置,大小,顏色,是否有焦點,是否被啟用等等。當然這個類中還提供了非常多的基礎函數,用於重載來實現子控件,如獲取控件名稱和ClassName,是否顯示等等。
另外為了方便從XML中直接解析出控件的各個屬性,這個類中還在提供了一個SetAttribute的方法,傳入字符串的屬性名稱和值對特定的屬性進行設置,內部其實就是挨個比較字符串去完成的,所以平時使用的時候就還是不要使用的比較好了,因為每個屬性實際上都有特定的方法來獲取和設置。
另外每個控件中還有幾個事件管理的對象——CEventSource,這些對象會在特定的時機被觸發,如OnInit,調用其中保存的各個回調函數。
1.2.2. 容器基類:CContainerUI
有了基本的控件基類之后,我們就需要容器來將他管理起來,這個容器就是CContainerUI,其內部用一個數組來保存所有的CControlUI的對象,后續的所有工作,就都是基於這個對象來進行的了。
這樣在CContainerUI里面,主要實現了一下幾個功能:
- 子控件的查找:CContainerUI::FindControl
- 子控件的生命周期管理:是否銷毀(在Remove的時候自動銷毀) / 是否延遲銷毀(交給CPaintMangerUI去一起銷毀)。
- 滾動條:所有的容器都支持滾動條,在其內部會對鍵盤和鼠標滾輪事件進行處理(CContainerUI::DoEvent),對其內部所有的元素調整位置,最后在繪制的時候實現滾動的效果
- 繪制:由於容器中有很多元素,所以為了加快容器的繪制,繪制的時候會獲取其真正需要繪制的區域,如果子控件不在此區域中,那么就不予繪制了
而對於這些控件的繪制實現以及相關使用,在后續具體進一步學習中再深入詳解。
2. 框架基本流程
框架的基本流程實際上類似Win32創建窗口流程,如果對於Win32比較熟悉,這部分可以很快掌握。
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { CPaintManagerUI::SetInstance(hInstance); // 第一步: 實例句柄與渲染類關聯 CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T("skin")); HRESULT Hr = ::CoInitialize(NULL); // 第二步:初始化COM庫, 為加載COM庫提供支持 if( FAILED(Hr) ) return 0; CMainFrameWnd* pFrame = new CMainFrameWnd(); // 第三步:創建窗口類 if( pFrame == NULL ) return 0; pFrame->Create(NULL, _T("主程序"), UI_WNDSTYLE_FRAME, 0L, 0, 0, 800, 600); // 第四步:注冊窗口類與創建窗口 // 實際上這里調用Create操作和Win32創建窗體一樣,內部實際上做了以下操作: // -> RegisterSuperclass (注冊一個超類 即已有一個窗口類的基上再注冊一個窗口類) // -> RegisterWindowClass (注冊窗口類) // -> ::CreateWindowEx (創建窗口,此時觸發 WM_CREATE 消息) // -> HandleMessage ( WM_CREATE消息處理OnCreate) pFrame->CenterWindow(); // 第五步:窗口居中顯示 ::ShowWindow(*pFrame, SW_SHOW); CPaintManagerUI::MessageLoop(); // 第六步:處理消息循環 ::CoUninitialize(); // 第七部:退出程序並釋放COM庫 return 0; }
3. 元素創建機制
第一步:響應WM_CREATE消息;
第二步:主窗口類與窗口句柄關聯;
m_pm.Init(m_hWnd)
第三步:加載XML並動態創建界面無素,與布局界面元素
CDialogBuilder builder;</span> CDialogBuilderCallbackEx cb;</span> CControlUI* pRoot =builder.Create(_T("skin.xml"), (UINT)0, &cb, &m_pm);
第四步:附加控件到HASH表
PaintManagerUI::AttachDialog
InitControls
FindControl
__FindControlFromNameHash
pManager->m_mNameHash.Insert
第五步:添加通知處理
CPaintManagerUI::AddNotifier
第六步:窗口的繪制(以上是窗口的創建過程,通過xml,所有控件都被加載到CPaintManagerUI)
CPaintManagerUI響應WM_PAINT消息,開始雙緩存繪圖 m_pRoot->DoPaint繪背景圖 CControlUI::DoPaint CRenderEngine 真正的繪圖類 pPostPaintControl->DoPostPaint 在背景圖上繪制控件 ::BitBlt 把離屏視圖畫到主屏上
4.消息處理機制
第一步:注冊消息處理函數
在CWindowWnd注冊窗口(RegisterWindowClass())里,注冊消息回調函數(__WndProc);
第二步:消息分發
消息回調函數(處理所有系統發送的消息),然后回調函數通過子類的CMainFrameWnd::HandleMessage對消息進行分發。
非窗口消息通過CMainFrameWnd::HandleMessage調用CPaintManagerUI::MessageHandler進行分發。
第三步:消息循環
在CPaintManagerUI類的MessageLoop處理消息循環;
接收到消息以后,進入消息回調函數(__WndProc);
(注:以下內容以鼠標單機Button事件為例)
第四步:處理控件消息
鼠標按下時(WM_LBUTTONDOWN),查找鼠標點擊的控件。
處理控件的鼠標按下消息:通過調用基類CControlUI:: DoEvent,引起子類如CButtonUI::DoEvent事件。
子類的DoEvent對不同類型的事件進行處理。通過CPaintManagerUI:: SendNotify回調控件注冊的事件。