大家好,又见面了,我是你们的朋友全栈君。
duilib是一个开源的DirectUI界面库,简洁但是功能强大。而且还是BSD的license,所以即便是在商业上,大家也可以安心使用。 现在大家可以从这个网站获取到他们所有的源码:http://code.google.com/p/duilib/
为了让我们能更简单的了解其机制,我们按照如下顺序一步一步的来对他进行观察:
以下是duilib工程带的一副总体设计图,在看代码之前看看这幅图,对看代码会很有帮助。 duilib:
由于duilib没有对外部的任何库进行依赖,所以在其内部实现了很多用于支撑项目的基础类,这些类分布在Util文件夹中:
上面这些类看名字就基本能够理解其具体的含义了,当然除了基本的基础库,还有一些和窗口使用相关的工具的封装:
控件库在duilib的实现中被分为了两块:Core和Control:
Core部分和控件相关的类图非常简单: duilib-core:
CControlUI在整个控件体系中非常重要,它是所有控件的基类,也是组成控件树的基本元素,控件树中所有的节点都是一个CControlUI。 他基本包括了所有控件公共的属性,如:位置,大小,颜色,是否有焦点,是否被启用,等等等等。当然这个类中还提供了非常多的基础函数,用于重载来实现子控件,如获取控件名称和ClassName,是否显示,等等等等。 另外为了方便从XML中直接解析出控件的各个属性,这个类中还在提供了一个SetAttribute的方法,传入字符串的属性名称和值对特定的属性进行设置,内部其实就是挨个比较字符串去完成的,所以平时使用的时候就还是不要使用的比较好了,因为每个属性实际上都有特定的方法来获取和设置。 另外每个控件中还有几个事件管理的对象——CEventSource,这些对象会在特定的时机被触发,如OnInit,调用其中保存的各个回调函数。
这里我们就碰到一个问题,控件树中的每一个节点都是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 }
有了基本的控件基类之后,我们就需要容器来将他管理起来,这个容器就是CContainerUI,其内部用一个数组来保存所有的CControlUI的对象,后续的所有工作,就都是基于这个对象来进行的了。 这样在CContainerUI里面,主要实现了一下几个功能:
有了普通的基类和容器的基类之后,我们就可以在其之上搭建控件了。其类图大致如下: duilib-control:
duilib实现了非常多的基本控件,他们分布在Control文件夹下,每一个头文件就是一个控件,主要有:
除了基本控件之外,duilib为了辅助大家对界面元素进行布局,还在中间实现了专门用于Layout的元素:
绘制控件实际上有很多代码都是可以抽取出来的,比如:九宫格拉伸图片,平铺图片等等工作,我们实际上都不需要每次都去重写。所以这部分代码被抽取出来,形成了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 };
当所有这些基本的控件都准备好了之后,我们就只要将这些控件管理起来,这样一个基本的控件库就完成了,而这个管理就是CPaintManagerUI来负责的。 在duilib中,一个Windows的原生窗口和一个CPaintManagerUI一一对应。其主要负责如下几个内容,后面会分开来细说,现在先了解一个概念就行:
为了实现上面这些功能,其中有几个用于管理控件和资源的关键的数据结构:
这些结构基本都可以看作是一堆列表和Map,这样可以用其来实现控件和资源的管理了。
有了控件,现在我们的问题是,如何将原生的窗口消息分发给界面中所有的控件,使其行为和原生的一样呢?
在duilib中,用来表示窗口的最基础的类是CWindowWnd,在这个类中实现了如下基本的内容:
duilib通过这个类,将原生窗口的消息分发给其派生类,最后传给整个控件体系。另外在duilib中,需要进行消息处理的基本控件,都是从这个类继承出来的。
一旦我们使用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;
通过上面这个最简单的例子,我们基本可以猜到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做处理,分发鼠标消息到对应的子控件上,可能是还没有完善的原因。
除了Event以外,CPaintManagerUI还提供了其他几种用于处理消息的方法:
这里需要注意的是:PreMessageFilter和TranslateAccelerator是通过全局数组来实现的,这并不符合多线程的窗口编程要求,所以duilib对多线程的支持并不是很好!
为了简化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中的皮肤文件主要有几个部分组成:
我们把这些资源放在一个文件夹中,这样就形成了基础的皮肤包。当然我们还可以将其组合成一个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中统一管理,如字体和图片等等。
由于项目里面实在是带了太多太多的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,字符串指针映射数组。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/167731.html原文链接:https://javaforall.cn