開源的DirectUI界面庫


1. duilib簡介

duilib是一個開源的DirectUI界面庫,簡潔但是功能強大。而且還是BSD的license,所以即便是在商業上,大家也可以安心使用。
現在大家可以從這個網站獲取到他們所有的源碼:http://code.google.com/p/duilib/

為了讓我們能更簡單的了解其機制,我們按照如下順序一步一步的來對他進行觀察:

  1. 工具庫:用於支撐整個項目的基礎
  2. 控件庫:這是dui最關鍵的部分之一,相信也是大家最關注的部分之一,另外這里也來看看它是如何管理這些控件的
  3. 消息流轉:有了控件庫,我們需要將Windows窗口的原生消息流轉給這些控件,另外在這里也來看看Focus,Capture等等的實現
  4. 資源組織和皮膚加載:有了上面所有的這些,我們再來看看它是如何自動創建皮膚的
  5. 簡單使用:最后,來看看到底要如何使用它

以下是duilib工程帶的一副總體設計圖,在看代碼之前看看這幅圖,對看代碼會很有幫助。
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。

 
1 LPVOID CControlUI::GetInterface(LPCTSTR pstrName)
2 {
3     if( _tcscmp(pstrName, _T("Control")) == 0 ) return this;
4     return NULL;
5 }

 

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下。在這個里面,我們可以看到很多的用於繪制方法。

 
 1 class UILIB_API CRenderEngine
 2 {
 3 public:
 4     // ......
 5     static void DrawLine(HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor);
 6     static void DrawRect(HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor);
 7     static void DrawRoundRect(HDC hDC, const RECT& rc, int width, int height, int nSize, DWORD dwPenColor);
 8     static void DrawText(HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, \
 9         DWORD dwTextColor, int iFont, UINT uStyle);
10     static void DrawHtmlText(HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText,
11         DWORD dwTextColor, RECT* pLinks, CDuiString* sLinks, int& nLinkRects, UINT uStyle);
12     // ......
13 };

 


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等其他的庫一樣,在此對原始的窗口消息進行處理。

 
1 LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
2 {
3     return ::CallWindowProc(m_OldWndProc, m_hWnd, uMsg, wParam, lParam);
4 }

 

當然如果我們覺得這樣麻煩,我們也可以使用CPaintManagerUI來對其進行默認處理。我們上面提到CPaintManagerUI還會對所有的控件進行管理,這樣,消息就傳遞給了窗口內部特定的控件了。
這些默認處理集中在CPaintManagerUI::MessageHandler()中,其內部會對很多窗口消息進行處理,並將其分發到對應的控件上去,比如對WM_LBUTTONDOWN的處理。

 
 1 case WM_LBUTTONDOWN:
 2     {
 3         // ......
 4         POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
 5         m_ptLastMousePos = pt;
 6         CControlUI* pControl = FindControl(pt);
 7         // ......
 8         TEventUI event = { 0 };
 9         event.Type = UIEVENT_BUTTONDOWN;
10         // ......
11         pControl->Event(event);
12     }
13     break;

 

4.2.1. Focus & Capture

通過上面這個最簡單的例子,我們基本可以猜到duilib對Focus和Capture的處理方法了:用一個成員變量保存對應的控件,在消息到達時直接轉發消息。
在CPaintMainagerUI中,大家可以找到一個成員變量:m_pFocus,這個就是用來保存焦點控件的。在WM_KEYDOWN等鍵盤消息發生時,duilib就會模擬Windows行為,將消息直接轉給當前Focus的控件。

 
 1 case WM_KEYDOWN:
 2     {
 3         if( m_pFocus == NULL ) break;
 4         TEventUI event = { 0 };
 5         event.Type = UIEVENT_KEYDOWN;
 6         // ...
 7         m_pFocus->Event(event);
 8         // ...
 9     }
10     break;

 

但是很奇怪的是,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     // ......

 

  5. 資源組織和皮膚加載

好了,現在我們已經有了控件管理和控件庫,現在我們需要讓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,字符串指針映射數組。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM