热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

duilib学习笔记

2012年11月3日—AspJ原创文章,转载请注明:转载自SoulApogee本文链接地址:duilib学习笔记前段时间对皮肤引擎比较感兴




原创文章,转载请注明:转载自Soul Apogee
本文链接地址:duilib学习笔记

前段时间对皮肤引擎比较感兴趣,于是在VS第一人称快的无法直视的dot大神推荐下,看了一个小巧又好用的皮肤引擎:duilib。

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:
duilib-core

3.1. 控件基类:CControlUI

CControlUI在整个控件体系中非常重要,它是所有控件的基类,也是组成控件树的基本元素,控件树中所有的节点都是一个CControlUI。
他基本包括了所有控件公共的属性,如:位置,大小,颜色,是否有焦点,是否被启用,等等等等。当然这个类中还提供了非常多的基础函数,用于重载来实现子控件,如获取控件名称和ClassName,是否显示,等等等等。
另外为了方便从XML中直接解析出控件的各个属性,这个类中还在提供了一个SetAttribute的方法,传入字符串的属性名称和值对特定的属性进行设置,内部其实就是挨个比较字符串去完成的,所以平时使用的时候就还是不要使用的比较好了,因为每个属性实际上都有特定的方法来获取和设置。
另外每个控件中还有几个事件管理的对象——CEventSource,这些对象会在特定的时机被触发,如OnInit,调用其中保存的各个回调函数。

3.1.1. 控件类型转换

这里我们就碰到一个问题,控件树中的每一个节点都是CControlUI,但是其实这些节点可能是文字,可能是图像,也有可能是列表,那么他怎么在这些控件指针之间进行转换呢?
强制转型不是一个好的选择,duilib中使用的是CControlUI::GetInterface,传入一个字符串,传出指向控件的指针。类似于COM的QueryInterface。

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

3.2. 容器基类:CContainerUI

有了基本的控件基类之后,我们就需要容器来将他管理起来,这个容器就是CContainerUI,其内部用一个数组来保存所有的CControlUI的对象,后续的所有工作,就都是基于这个对象来进行的了。
这样在CContainerUI里面,主要实现了一下几个功能:

  • 子控件的查找:CContainerUI::FindControl
  • 子控件的生命周期管理:是否销毁(在Remove的时候自动销毁) / 是否延迟销毁(交给CPaintMangerUI去一起销毁)。
  • 滚动条:所有的容器都支持滚动条,在其内部会对键盘和鼠标滚轮事件进行处理(CContainerUI::DoEvent),对其内部所有的元素调整位置,最后在绘制的时候实现滚动的效果
  • 绘制:由于容器中有很多元素,所以为了加快容器的绘制,绘制的时候会获取其真正需要绘制的区域,如果子控件不在此区域中,那么就不予绘制了

3.3. 控件实现

有了普通的基类和容器的基类之后,我们就可以在其之上搭建控件了。其类图大致如下:
duilib-control:
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
2
3
4
5
6
7
8
9
10
11
12
13
class UILIB_API CRenderEngine
{
public:
    // ......
    static void DrawLine(HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor);
    static void DrawRect(HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor);
    static void DrawRoundRect(HDC hDC, const RECT& rc, int width, int height, int nSize, DWORD dwPenColor);
    static void DrawText(HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, \
        DWORD dwTextColor, int iFont, UINT uStyle);
    static void DrawHtmlText(HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText,
        DWORD dwTextColor, RECT* pLinks, CDuiString* sLinks, int& nLinkRects, UINT uStyle);
    // ......
};

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
2
3
4
LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return ::CallWindowProc(m_OldWndProc, m_hWnd, uMsg, wParam, lParam);
}

当然如果我们觉得这样麻烦,我们也可以使用CPaintManagerUI来对其进行默认处理。我们上面提到CPaintManagerUI还会对所有的控件进行管理,这样,消息就传递给了窗口内部特定的控件了。
这些默认处理集中在CPaintManagerUI::MessageHandler()中,其内部会对很多窗口消息进行处理,并将其分发到对应的控件上去,比如对WM_LBUTTONDOWN的处理。

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

4.2.1. Focus & Capture

通过上面这个最简单的例子,我们基本可以猜到duilib对Focus和Capture的处理方法了:用一个成员变量保存对应的控件,在消息到达时直接转发消息。
在CPaintMainagerUI中,大家可以找到一个成员变量:m_pFocus,这个就是用来保存焦点控件的。在WM_KEYDOWN等键盘消息发生时,duilib就会模拟Windows行为,将消息直接转给当前Focus的控件。

?
1
2
3
4
5
6
7
8
9
10
case WM_KEYDOWN:
    {
        if( m_pFocus == NULL ) break;
        TEventUI event = { 0 };
        event.Type = UIEVENT_KEYDOWN;
        // ...
        m_pFocus->Event(event);
        // ...
    }
    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
2
3
4
5
6
7
8
9
10
11
12
13
class UILIB_API WindowImplBase
    : public CWindowWnd
    , public CNotifyPump
    , public INotifyUI
    , public IMessageFilterUI
    , public IDialogBuilderCallback
{
    // ......
    virtual UINT GetClassStyle() const;
    // ......
    virtual LRESULT OnClose(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
    virtual LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
    // ......

5. 资源组织和皮肤加载

好了,现在我们已经有了控件管理和控件库,现在我们需要让UI框架来帮忙组织这些资源,并且自动的来帮我们创建皮肤,减少我们的开发量。
duilib中的皮肤文件主要有几个部分组成:

  • xml描述文件:描述窗口中控件的布局和样式
  • 各种资源如图片

我们把这些资源放在一个文件夹中,这样就形成了基础的皮肤包。当然我们还可以将其组合成一个zip包,从而加快IO访问,但是修改起来就会相对麻烦。所以我们可以在debug中使用前者,而在release中使用后者。
我们可以在bin\skin下面找到duilib中自带demo的所有的皮肤包。

皮肤中,最关键的部分就是这个xml描述文件了,一个xml描述文件对应着一个窗口的信息,如:控件的类型和样式等等。为了有一个直观的印象,我截取了duilib中ListDemo的xml描述文件的一部分放在这里:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
xml version="1.0" encoding="utf-8"?>
<Window caption&#61;"0,0,0,30" roundcorner&#61;"5,5,5,5" sizebox&#61;"4,4,4,4" mininfo&#61;"600,320" showdirty&#61;"true">
...
<VerticalLayout bkimage&#61;"file&#61;&#39;bg.png&#39; corner&#61;&#39;10,100,10,10&#39; hole&#61;&#39;true&#39;" bkcolor&#61;"#FF313C00">
...
    <HorizontalLayout height&#61;"35" inset&#61;"0,4,0,8">
        <VerticalLayout inset&#61;"8,4,2,2" width&#61;"80">
            <Text text&#61;"Domain/ip:" textcolor&#61;"#000000" font&#61;"1">Text>
        VerticalLayout>
        <VerticalLayout>
            <Edit height&#61;"23" text&#61;"List控件添加使用案例&#xff0c;每行可以响应事件" bordercolor&#61;"#C6CFD8" name&#61;"input" bkimage&#61;"file&#61;&#39;search_bg.png&#39; source&#61;&#39;0,0,258,23&#39; corner&#61;&#39;1,1,1,1&#39;"/>
        VerticalLayout>
        <VerticalLayout width&#61;"80">
            <Button name&#61;"btn" text&#61;"Search" font&#61;"0" float&#61;"true" pos&#61;"5,0,63,23" maxwidth&#61;"63" maxheight&#61;"23" normalimage&#61;"file&#61;&#39;button.png&#39; source&#61;&#39;0,0,63,23&#39;" hotimage&#61;"file&#61;&#39;button.png&#39; source&#61;&#39;0,23,63,46&#39;" pushedimage&#61;"file&#61;&#39;button.png&#39; source&#61;&#39;0,23,63,46&#39;"/>
        VerticalLayout>
    HorizontalLayout>
...
VerticalLayout>
Window>

为了通过配置文件自动创建皮肤&#xff0c;duilib提供了一个类&#xff1a;CDialogBuilder&#xff08;DuiLib\Core\UIDlgBuilder.h&#xff09;。
这个类提供了从皮肤包&#xff08;文件夹和zip格式&#xff09;中的xml中创建皮肤的方法&#xff1a;CDialogBuilder::Create。内部实际上就是一个xml的解析&#xff0c;依次创建各式控件。
除了创建控件&#xff0c;这个类还将一些可以复用的资源提取出来放入CPaintManagerUI中统一管理&#xff0c;如字体和图片等等。

6. 简单使用

由于项目里面实在是带了太多太多的demo&#xff0c;而且在duilib的工程中&#xff0c;还有一个doc的目录&#xff0c;里面也非常详细的描述了要如何使用duilib来创建一个简单的工程
所以关于duilib的简单使用&#xff0c;这里就不再详述了&#xff0c;这里就只列出GameDemo的main函数&#xff0c;这个函数非常的简单&#xff0c;但是已经基本可以表达了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);
    CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() &#43; _T("skin"));
    CPaintManagerUI::SetResourceZip(_T("GameRes.zip"));
    HRESULT Hr &#61; ::CoInitialize(NULL);
    if( FAILED(Hr) ) return 0;
    CGameFrameWnd* pFrame &#61; new CGameFrameWnd();
    if( pFrame &#61;&#61; NULL ) return 0;
    pFrame->Create(NULL, _T(""), UI_WNDSTYLE_FRAME, 0L, 0, 0, 1024, 738);
    pFrame->CenterWindow();
    ::ShowWindow(*pFrame, SW_SHOWMAXIMIZED);
    CPaintManagerUI::MessageLoop();
    ::CoUninitialize();
    return 0;
}

7. 总结

总的来说&#xff0c;duilib还是一个很小巧好用的皮肤引擎的&#xff0c;但是他仍然有其不好的地方&#xff1a;对多线程的支持不好&#xff0c;不支持动画。但是无论如何&#xff0c;它还是不错的&#xff0c;所以如果你已经看到了这里&#xff0c;那么接下来跑到vs里面建一个工程&#xff0c;玩一把才是正经事&#xff5e;



推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • iOS Swift中如何实现自动登录?
    本文介绍了在iOS Swift中如何实现自动登录的方法,包括使用故事板、SWRevealViewController等技术,以及解决用户注销后重新登录自动跳转到主页的问题。 ... [详细]
  • C#多线程解决界面卡死问题的完美解决方案
    当界面需要在程序运行中不断更新数据时,使用多线程可以解决界面卡死的问题。一个主线程创建界面,使用一个子线程执行程序并更新主界面,可以避免卡死现象。本文分享了一个例子,供大家参考。 ... [详细]
  • java.lang.Class.getDeclaredMethod()方法java.lang.Class.getDeclaredMethod()方法用法实例教程-方法返回一个Met ... [详细]
  • 百度地图   绘制东莞东城地图示例
    先上图:index.html ... [详细]
  • 前端库Bootstrap框架:「11]使用 span 创建行内元素
    前端库Bootstrap框架:「11]使用 span 创建行内元素 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • 本篇文章为大家展示了input语句的作用有哪些,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。HTML标签 ... [详细]
author-avatar
未知的明天
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有