1. duilib簡介
duilib是一個開源的DirectUI界面庫,簡潔但是功能強大。而且還是BSD的license,所以即便是在商業上,大家也可以安心使用。
現在大家可以從這個網站獲取到他們所有的源碼:http://code.google.com/p/duilib/
為了讓我們能更簡單的了解其機制,我們按照如下順序一步一步的來對他進行觀察:
- 工具庫:用於支撐整個項目的基礎
- 控件庫:這是dui最關鍵的部分之一,相信也是大家最關注的部分之一,另外這里也來看看它是如何管理這些控件的
- 消息流轉:有了控件庫,我們需要將Windows窗口的原生消息流轉給這些控件,另外在這里也來看看Focus,Capture等等的實現
- 資源組織和皮膚加載:有了上面所有的這些,我們再來看看它是如何自動創建皮膚的
- 簡單使用:最后,來看看到底要如何使用它
以下是duilib工程帶的一副總體設計圖,在看代碼之前看看這幅圖,對看代碼會很有幫助。
duilib:
2. 工具庫
由於duilib沒有對外部的任何庫進行依賴,所以在其內部實現了很多用於支撐項目的基礎類,這些類分布在Util文件夾中:
- UI相關:CPoint / CSize / CDuiRect
- 簡單容器:CStdPtrArray / CStdValArray / CStdString / CStdStringPtrMap
上面這些類看名字就基本能夠理解其具體的含義了,當然除了基本的基礎庫,還有一些和窗口使用相關的工具的封裝:
- 窗口工具:WindowImplBase,這個工具我們在這里不詳述,后面會再次提到。
3. 控件庫
控件庫在duilib的實現中被分為了兩塊:Core和Control:
- Core中包含的是所有控件公用的部分,里面主要是一些基類和繪制的封裝。
- Control中包含的就是各個不同的控件的行為了。
Core部分和控件相關的類圖非常簡單:
duilib-core:
3.1. 控件基類:CControlUI
CControlUI在整個控件體系中非常重要,它是所有控件的基類,也是組成控件樹的基本元素,控件樹中所有的節點都是一個CControlUI。
他基本包括了所有控件公共的屬性,如:位置,大小,顏色,是否有焦點,是否被啟用,等等等等。當然這個類中還提供了非常多的基礎函數,用於重載來實現子控件,如獲取控件名稱和ClassName,是否顯示,等等等等。
另外為了方便從XML中直接解析出控件的各個屬性,這個類中還在提供了一個SetAttribute的方法,傳入字符串的屬性名稱和值對特定的屬性進行設置,內部其實就是挨個比較字符串去完成的,所以平時使用的時候就還是不要使用的比較好了,因為每個屬性實際上都有特定的方法來獲取和設置。
另外每個控件中還有幾個事件管理的對象——CEventSource,這些對象會在特定的時機被觸發,如OnInit,調用其中保存的各個回調函數。
3.1.1. 控件類型轉換
這里我們就碰到一個問題,控件樹中的每一個節點都是CControlUI,但是其實這些節點可能是文字,可能是圖像,也有可能是列表,那么他怎么在這些控件指針之間進行轉換呢?
強制轉型不是一個好的選擇,duilib中使用的是CControlUI::GetInterface,傳入一個字符串,傳出指向控件的指針。類似於COM的QueryInterface。
3.2. 容器基類:CContainerUI
有了基本的控件基類之后,我們就需要容器來將他管理起來,這個容器就是CContainerUI,其內部用一個數組來保存所有的CControlUI的對象,后續的所有工作,就都是基於這個對象來進行的了。
這樣在CContainerUI里面,主要實現了一下幾個功能:
- 子控件的查找:CContainerUI::FindControl
- 子控件的生命周期管理:是否銷毀(在Remove的時候自動銷毀) / 是否延遲銷毀(交給CPaintMangerUI去一起銷毀)。
- 滾動條:所有的容器都支持滾動條,在其內部會對鍵盤和鼠標滾輪事件進行處理(CContainerUI::DoEvent),對其內部所有的元素調整位置,最后在繪制的時候實現滾動的效果
- 繪制:由於容器中有很多元素,所以為了加快容器的繪制,繪制的時候會獲取其真正需要繪制的區域,如果子控件不在此區域中,那么就不予繪制了
3.3. 控件實現
有了普通的基類和容器的基類之后,我們就可以在其之上搭建控件了。其類圖大致如下:
duilib-control:
3.3.1. 基本控件
duilib實現了非常多的基本控件,他們分布在Control文件夾下,每一個頭文件就是一個控件,主要有:
- CLabelUI / CTextUI / CEditUI / CRichEditUI
- CButtonUI / CCheckBoxUI / COptionUI (RadioButton)
- CScrollBarUI / CProgressUI / CSliderUI
- CListUI
- CDateTimeUI / CActiveXUI / CWebBrowserUI
3.3.2. Layout
除了基本控件之外,duilib為了輔助大家對界面元素進行布局,還在中間實現了專門用於Layout的元素:
- CChildLayoutUI
- CHorizontalLayoutUI / CVerticalLayoutUI / CTileLayoutUI:縱向排列,橫向排列格子排列
- CTabLayoutUI:Tab
3.3.3. 控件繪制
繪制控件實際上有很多代碼都是可以抽取出來的,比如:九宮格拉伸圖片,平鋪圖片等等工作,我們實際上都不需要每次都去重寫。所以這部分代碼被抽取出來,形成了CRenderEngine,這個類在Core/UIRender下。在這個里面,我們可以看到很多的用於繪制方法。
3.4. 控件管理:CPaintManagerUI
當所有這些基本的控件都准備好了之后,我們就只要將這些控件管理起來,這樣一個基本的控件庫就完成了,而這個管理就是CPaintManagerUI來負責的。
在duilib中,一個Windows的原生窗口和一個CPaintManagerUI一一對應。其主要負責如下幾個內容,后面會分開來細說,現在先了解一個概念就行:
- 控件管理
- 資源管理
- 轉化並分發Windows原生的窗口消息
為了實現上面這些功能,其中有幾個用於管理控件和資源的關鍵的數據結構:
- m_pRoot:保存根控件的節點
- m_mNameHash:保存控件名稱Hash和控件對象指針的關系
- m_mOptionGroup:保存控件相關的Group,這個Group並不是TabOrder,他用於實現Option控件
- m_aCustomFonts:用來管理字體資源
- m_mImageHash:用來管理圖片資源
這些結構基本都可以看作是一堆列表和Map,這樣可以用其來實現控件和資源的管理了。
4. 消息流轉
有了控件,現在我們的問題是,如何將原生的窗口消息分發給界面中所有的控件,使其行為和原生的一樣呢?
4.1. 窗口基礎類:CWindowWnd
在duilib中,用來表示窗口的最基礎的類是CWindowWnd,在這個類中實現了如下基本的內容:
- 原生窗口的創建(CWindowWnd::Create)
- Subclass(CWindowWnd::Subclass)
- 最基本的消息處理函數(CWindowWnd::__WndProc)和消息分發(CWindowWnd::HandleMessage)
- 模態窗口(CWindowWnd::ShowModal)
duilib通過這個類,將原生窗口的消息分發給其派生類,最后傳給整個控件體系。另外在duilib中,需要進行消息處理的基本控件,都是從這個類繼承出來的。
4.2. 消息分發
一旦我們使用CWindowWnd類創建了窗口之后,消息就會通過CWindowWnd::HandleMessage進行分發,我們可以和WTL等其他的庫一樣,在此對原始的窗口消息進行處理。
當然如果我們覺得這樣麻煩,我們也可以使用CPaintManagerUI來對其進行默認處理。我們上面提到CPaintManagerUI還會對所有的控件進行管理,這樣,消息就傳遞給了窗口內部特定的控件了。
這些默認處理集中在CPaintManagerUI::MessageHandler()中,其內部會對很多窗口消息進行處理,並將其分發到對應的控件上去,比如對WM_LBUTTONDOWN的處理。
4.2.1. Focus & Capture
通過上面這個最簡單的例子,我們基本可以猜到duilib對Focus和Capture的處理方法了:用一個成員變量保存對應的控件,在消息到達時直接轉發消息。
在CPaintMainagerUI中,大家可以找到一個成員變量:m_pFocus,這個就是用來保存焦點控件的。在WM_KEYDOWN等鍵盤消息發生時,duilib就會模擬Windows行為,將消息直接轉給當前Focus的控件。
但是很奇怪的是,duilib里面並沒有對Capture做處理,分發鼠標消息到對應的子控件上,可能是還沒有完善的原因。
4.2.2. 其他消息分發方式
除了Event以外,CPaintManagerUI還提供了其他幾種用於處理消息的方法:
- Notifier:在窗口上處理一些控件的邏輯,可以將其看成和WM_NOTIFY差不多的功能
- PreMessageFilter:消息預處理,這個大家肯定不陌生了。
- PostPaint:繪制后的回調
- TranslateAccelerator:快捷鍵的處理
這里需要注意的是:PreMessageFilter和TranslateAccelerator是通過全局數組來實現的,這並不符合多線程的窗口編程要求,所以duilib對多線程的支持並不是很好!
4.3. WindowImplBase
為了簡化duilib的使用,庫中提供了一個非常方便的工具:WindowImplBase。
這個類將常用的功能封裝在其內部,比如Notifier和PreMessageFilter,並在其中提供了各種默認的虛回調函數,供派生類重載。通過這個類,我們可以非常方便的來實現一個簡單的界面。
1 class UILIB_API WindowImplBase 2 : public CWindowWnd 3 , public CNotifyPump 4 , public INotifyUI 5 , public IMessageFilterUI 6 , public IDialogBuilderCallback 7 { 8 // ...... 9 virtual UINT GetClassStyle() const; 10 // ...... 11 virtual LRESULT OnClose(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); 12 virtual LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); 13 // ......
好了,現在我們已經有了控件管理和控件庫,現在我們需要讓UI框架來幫忙組織這些資源,並且自動的來幫我們創建皮膚,減少我們的開發量。
duilib中的皮膚文件主要有幾個部分組成:
- xml描述文件:描述窗口中控件的布局和樣式
- 各種資源如圖片
我們把這些資源放在一個文件夾中,這樣就形成了基礎的皮膚包。當然我們還可以將其組合成一個zip包,從而加快IO訪問,但是修改起來就會相對麻煩。所以我們可以在debug中使用前者,而在release中使用后者。
我們可以在bin\skin下面找到duilib中自帶demo的所有的皮膚包。
皮膚中,最關鍵的部分就是這個xml描述文件了,一個xml描述文件對應着一個窗口的信息,如:控件的類型和樣式等等。為了有一個直觀的印象,我截取了duilib中ListDemo的xml描述文件的一部分放在這里:
1 <?xml version="1.0" encoding="utf-8"?> 2 <Window caption="0,0,0,30" roundcorner="5,5,5,5" sizebox="4,4,4,4" mininfo="600,320" showdirty="true"> 3 ... 4 <VerticalLayout bkimage="file='bg.png' corner='10,100,10,10' hole='true'" bkcolor="#FF313C00"> 5 ... 6 <HorizontalLayout height="35" inset="0,4,0,8"> 7 <VerticalLayout inset="8,4,2,2" width="80"> 8 <Text text="Domain/ip:" textcolor="#000000" font="1"></Text> 9 </VerticalLayout> 10 <VerticalLayout> 11 <Edit height="23" text="List控件添加使用案例,每行可以響應事件" bordercolor="#C6CFD8" name="input" bkimage="file='search_bg.png' source='0,0,258,23' corner='1,1,1,1'"/> 12 </VerticalLayout> 13 <VerticalLayout width="80"> 14 <Button name="btn" text="Search" font="0" float="true" pos="5,0,63,23" maxwidth="63" maxheight="23" normalimage="file='button.png' source='0,0,63,23'" hotimage="file='button.png' source='0,23,63,46'" pushedimage="file='button.png' source='0,23,63,46'"/> 15 </VerticalLayout> 16 </HorizontalLayout> 17 ... 18 </VerticalLayout> 19 </Window>
為了通過配置文件自動創建皮膚,duilib提供了一個類:CDialogBuilder(DuiLib\Core\UIDlgBuilder.h)。
這個類提供了從皮膚包(文件夾和zip格式)中的xml中創建皮膚的方法:CDialogBuilder::Create。內部實際上就是一個xml的解析,依次創建各式控件。
除了創建控件,這個類還將一些可以復用的資源提取出來放入CPaintManagerUI中統一管理,如字體和圖片等等。
6. 簡單使用
由於項目里面實在是帶了太多太多的demo,而且在duilib的工程中,還有一個doc的目錄,里面也非常詳細的描述了要如何使用duilib來創建一個簡單的工程。
所以關於duilib的簡單使用,這里就不再詳述了,這里就只列出GameDemo的main函數,這個函數非常的簡單,但是已經基本可以表達了。
1 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow) 2 { 3 CPaintManagerUI::SetInstance(hInstance); 4 CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T("skin")); 5 CPaintManagerUI::SetResourceZip(_T("GameRes.zip")); 6 7 HRESULT Hr = ::CoInitialize(NULL); 8 if( FAILED(Hr) ) return 0; 9 10 CGameFrameWnd* pFrame = new CGameFrameWnd(); 11 if( pFrame == NULL ) return 0; 12 pFrame->Create(NULL, _T(""), UI_WNDSTYLE_FRAME, 0L, 0, 0, 1024, 738); 13 pFrame->CenterWindow(); 14 ::ShowWindow(*pFrame, SW_SHOWMAXIMIZED); 15 16 CPaintManagerUI::MessageLoop(); 17 18 ::CoUninitialize(); 19 return 0; 20 }
一、核心類
1. CWindowWnd,窗口對象管理父類,主要作用:
1) 創建窗口。
2) 窗口消息過程處理。
3) 提供窗口子類化與超類化接口。
2. CDialogBuilder,控件布局類,主要作用:
1) 讀取XML腳本,分析腳本,構建控件樹。
2) 創建控件對象。
3. CPaintManagerUI,窗口消息及圖形繪制管理器類,與窗口綁定,主要作用:
1) 繪制控件。
2) 消息管理。
3) 事件通知。
4. INotifyUI,事件通知抽象類,主要作用:
1) 重載Notify虛函數,處理事件通知。
二、控件類
1. CControlUI,控件管理父類,主要作用:
1) 控件的通用基類,提供控件通用屬性管理。
2. CLabelUI,靜態標簽類,父類CControlUI。
3. CButtonUI,按鈕類,父類CLabelUI。
4. COptionUI,選擇按鈕類,父類CButtonUI。
5. CTextUI,靜態文本類,父類CLabelUI。
6. CProgressUI,進度條類,父類CLabelUI。
7. CSliderUI,父類CProgressUI。
8. CEditUI,編輯框類,父類CLabelUI。
9. CListUI,列表框類,父類CVerticalLayoutUI、IListUI。
1) CListHeaderUI,父類CHorizontalLayoutUI。
2) CListHeaderItemUI,列表頭類,父類CControlUI。
3) CListTextElementUI,類表文本類,父類CListLabelElementUI。
4) CListLabelElementUI,父類CListElementUI。
10. CComboUI,組合框類,父類CContainerUI、IListOwnerUI。
11. CActiveXUI,ActiveX控件類,父類CControlUI、 IMessageFilterUI。
12. CContainerUI,容器類,父類CControlUI、IContainerUI。
13. CTabLayoutUI,選項頁布局類,父類CContainerUI。
14. CTileLayoutUI,父類CContainerUI。
15. CDialogLayoutUI,對話框布局類,父類CContainerUI。、
16. CVerticalLayoutUI,垂直布局類,父類CContainerUI。
17. CHorizontalLayoutUI,水平布局類,父類CContainerUI。
18. CListExpandElementUI,父類CListTextElementUI。
19. CListContainerElementUI,父類CContainerUI、IListItemUI。
三、輔助類
1. CStdPtrArray,指針數組。
2. CStdValArray,數據數組。
3. CStdString,字符串數組。
4. CStdStringPtrMap,字符串指針映射數組。