我之前也写过一片封装xml为一个容器的文章,只是写的很随意,仅仅贴出了一个demo的地址。

在群里还有一些刚刚接触duilib的朋友们问到duilib自定义控件的问题,这里我转载一篇redrain大佬的博文。主要是这篇文章写的太好了,我们直接参考理解就好,我写的肯定没这个好。原文地址:http://blog.csdn.net/zhuhongshu/article/details/45362751。需要注意redrain大佬的这篇文章写的时间比较早,是基于早期的duilib版本,新版的duilib略有改动,我在下文中已经进行了修改。

用Duilib开发界面时,很多情况下库自带的控件不满足需求,就需要基于Duilib建立自定义控件(自绘新的控件,或者用来封装win32的子窗体,来显示视频、网页等)。
在群里经常会有刚接触Duilib的朋友问题怎么建立自己的自定义控件,或者建立的控件无法正常创建出来。我简单写一篇博客,把创建自定义控件的完整过程,和一些注意事项说明一下。另外说一下如果把win32的子窗体封装为控件,希望能有帮助。
创建自定义控件包含两个过程:
1、继承现有的控件类创建新的控件类
2、让程序识别新的控件并可以在xml中使用
创建新的控件类:
首先从的现有的Duilib控件中选择一个最合适的控件类作为父类用来派生,比如你想自定义一个按钮,那么你可以从CButtonUI派生出新的类,然后重写几个接口。 因为CButtonUI控件本身就已经包含了normal、hot、pushed等状态,同时包含单击事件。

    一般来说,建立新控件后,最先应该重写的两个函数是GetClass和GetInterface。他们是用来区分控件的类型的虚函数,用于动态识别控件类型和做控件的类型转换。
从Duilib的自带控件上可以看出,那么GetClass函数返回的字符串一般是DUI_CTR_XXXX,这个经常用于duilib内部识别具体控件类型用。而GetInterface函数是根据传入的参数,是否与自身的字符串匹配,来决定能否把自己转换为需要的控件类型。GetInterface中用来匹配的字符串,应该与xml中的对应的控件的标签名称一致。
比如CButtonUI类,GetClass对应DUI_CTR_BUTTON,GetInterface对应DUI_CTR_BUTTON。这不是强制的,但是保持这个风格很重要!这里一般来说是使用DUI_CTR_BUTTON宏,也可以直接写DUI_CTR_BUTTON对应的字符串Button,不过不建议这样做。在自己的程序中如果用到相关的,最好也用宏,这样一般需要修改某个宏对应的字符串,仅改声明就行了。
理论上,完成这两个接口就创建好最基本的自定义控件了。但是为了让自定义控件的行为和外观更丰富,就需要重写更多的函数了,我这里把经常会重写的函数说明一下!
[cpp]  view plain  copy
  1. virtual LPVOID GetInterface(LPCTSTR pstrName);
    virtual UINT GetControlFlags() const;
    virtual HWND GetNativeWindow() const;
  2. virtual void SetPos(RECT rc, bool bNeedInvalidate = true);
  3. virtual void DoInit();
  4. virtual void DoEvent(TEventUI& event);
  5. virtual bool DoPaint(HDC hDC, const RECT& rcPaint, CControlUI* pStopControl);
  6. virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);
  7. virtual void SetInternVisible(bool bVisible = true); // 仅供内部调用,有些UI拥有窗口句柄,需要重写
 
 

以上列出的函数,是最常被重写的。

DoEvent函数:控件的核心函数,他是消息处理函数,用来处理Duilib封装过的各个消息,比如鼠标的移入移出、出现的悬停、单击双击、右击、滚轮滑动、获取焦点、设置光标等等。所以如果你的控件需要修改这些行为,必须重写这个函数,具体的处理方法可以参考Duilib现有的控件。
DoPaint函数:控件的核心函数,他是控件的绘制处理函数,当Duilib底层要重新绘制这个控件,或者控件自己调用Invalidata函数强制自己刷新时,这个函数就会被触发,在这个函数里完成了各种状态下的背景前景绘制,背景色绘制,文本绘制,边框绘制。而这个函数会调用PaintBkColor、PaintBkImage、PaintStatusImage、PaintText、PaintBorder等函数来完成各个绘制步骤。所以你可以根据需求来决定重写DoPaint或者只重写某几个PaintXXX函数。DoPaint函数经常和DoEvent函数结合使用,DoEvent得到了某个消息后,改变控件的状态,然后调用Invalidate函数让控件重绘。
SetAttribute函数:用于扩展自定义控件的属性,Duilib的控件本身已经包含name、text、bkimage等属性,如果要增加新属性,就需要重写此函数来扩展属性。
DoInit函数:当控件被添加到容器后,由容器调用的函数。在这里,整个Duilib程序框架已经完成,当需要做一些界面的初始操作时可以重写此函数,常见的用法就是在此建立Win32子窗体并且封装它,相关内容我在后面再说。
SetInternelVisible、SetPos:这几个函数同样也是,当控件封装了Win32子窗口后,重写这几个函数来控制子窗口的显示和隐藏、和位置。
这样就创建完成了自定义控件。
识别新控件:
自定义控件创建完毕后,需要做的就是让控件可以被xml布局识别出来。为此我们需要完成Duilib的IDialogBuilderCallback接口,重写这个接口中的CreateControl函数。
通常情况下,可以让窗体类继承IDialogBuilderCallback接口并且重写CreateControl(DuiLib自带的WindowImplBase窗体类已经继承了这个接口,如果是继承WindowImplBase的话就直接重写CreateControl就可以了)。函数处理方法是比较传入的字符串,根据字符串来决定返回什么控件的指针,这个传入的字符串就是xml文件中控件的标签,比如<Button />中的字符串Button。
习惯上,在xml中自定义控件的标签名称应该和控件的GetInterface中的判断字符串一致。这样,在解析xml过程中,当解析到 标签名为对应的字符串时,就会创建出对应的控件了。
实际上,谁来继承IDialogBuilderCallback接口肯定都可以,比如QQDemo里,是给自定义控件本身继承了这个接口。
当程序响应WM_CREATE消息时,会建立一个CDialogBuilder对象,并且调用他的Create方法来解析xml文件。
[cpp]  view plain  copy
  1. CControlUI* CDialogBuilder::Create(STRINGorID xml, LPCTSTR type, IDialogBuilderCallback* pCallback,
  2. CPaintManagerUI* pManager, CControlUI* pParent)

这个函数 的第一个参数指定为xml文件的路径;第二个参数一般指定为NULL,我这里不详解了;第三个参数,就是识别自定义控件的关键了,这个参数要指定为继承了IDialogBuilderCallback接口的类对象的指针,比如窗体类继承IDialogBuilderCallback,这个参数就填写窗体类对象的指针。只有填写了这个参数,自定义控件才会被识别,经常有人问自己的自定义控件为什么无法被识别。多数情况就是这里没处理好;第四个参数指定CPaintManagerUI类对象指针,这个肯定会伴随着窗体类对象一起存在。最后一个参数一般为NULL。

这几步都完成后,你的自定义控件就可以被xml布局正确的识别并创建了。至此,创建自定义控件的基本过程就完成了!如果有不明白的,可以多看看QQDemo等代码。
封装Win32控件或者Win32子窗口:
如果要给Duilib,增加一个视频播放控件,一般来说视频播放库都需要依赖一个子窗口。这时,就应该自定义个控件,并且封装维护一个子窗口了。
封装的子窗口有三种:第一种比较简单、单纯封装一个子窗口、让视频库一类的库依赖;第二种麻烦一些、封装子窗口、并且处理子窗口的消息;第三种和第二种类似、封装Win32的控件并且处理他的消息。
单纯封装子窗口:
这时就需要重写我之前提到的DoInit函数和SetVisbile等函数了。首先在自定义控件内声明HWND类型的m_hWnd成员变量来保存子窗体指针。
在DoInit函数里,调用CreateWindowEx函数,创建一个win32子窗体,并且用m_hWnd保存句柄。比如:
[cpp]  view plain  copy
  1. m_hhWnd = CreateWindow(_T("#32770"), _T("WndMediaDisplay"), WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, m_PaintManager.GetPaintWindow(), (HMENU)0, NULL, NULL);

然后在SetVisible等函数内控制子窗体的显示隐藏;在SetPos函数内控制子窗体的位置、限制在本控件的范围内。

这样就封装好了win32子窗口,然后可以把这个窗体句柄用于视频播放等。
封装子窗口并处理他的消息:
这时就比较麻烦了,参见Duilib的CEditUI控件等。我们需要继承CWindowWnd另外封装一个窗体类,窗体类的封装不属于本文范围,我就不细说了。重写窗体类的HandleMessage函数,来响应各种WM_XXX消息。
然后在我们的自定义控件内,不再声明HWND类型m_hWnd变量了,而是自定刚才的窗体类的对象。然后在DoInit函数内调用这个对应的Create函数函数来创建窗体类。然后同样还是维护这个窗体的显示隐藏、和位置。
关于这种控件的封装,可以参考我写的webkit内核浏览器控件、里面是完整的封装了Win32子窗体、并且处理了他的消息,用于显示webkit内核渲染的网页。地址: http://blog.csdn.net/zhuhongshu/article/details/38540711
封装Win32控件并处理他的消息:
这个可以参考CEditUI控件的处理代码,思想上和封装子窗口并处理消息是一样的。同样也可以参考webkit内核浏览器控件代码。不过与之不同的是,我们需要重写两个函数
[cpp]  view plain  copy
  1. LPCTSTR GetWindowClassName() const;
  2. LPCTSTR GetSuperClassName() const;
这里最主要的就是处理GetSuperClassName函数,这个函数的作用就是超类化,而封装子窗口并处理消息是子类化,这两个操作恰好相反。在GetSuperClassName函数内,要返回Win32控件对应的类名、Duilib检测到GetSuperClassName函数函数后就会创建Win32控件。这时我们处理HandleMessage函数,就可以处理到Win32控件的消息的。具体的处理逻辑请参见CEditUI控件。
额外说一点:
扩展到当前流行的wke,miniblink等流行的浏览器组件,包括cef等,要合并到duilib中使用:1. 使用WS_CHILD嵌入式真子窗口,具体demo请自己找,可以参考CEditUI控件。2.使用WS_POPUP弹出式真子窗口。3.浏览器组件使用OSR等方式,自己绘制到窗口dc上,这是无窗口控件。具体的使用哪一种自己根据需求。对应的demo还需要自己去找,我虽然3种方式都用过写过,但是没有剥离过demo,以后有机会了把3中都搞个demo再分享给大家吧。
总结:
差不多就说道这里了,把常见的自定义控件的基本步骤说明了一下,实际开发时还要多看Duilib的源码,才能称心如意的开发控件,希望对刚接触Duilib的朋友有帮助!

duilib创建自定义控件相关推荐

  1. Duilib创建添加自定义控件

    本篇参考资料:Duilib自定义控件博文(duilib开发基础:创建自定义控件的过程):http://blog.csdn.net/zhuhongshu/article/details/45362751 ...

  2. Android 第八课 创建自定义控件

    常用控件和布局的继承结构,如下图: (待续....) 所有的控件都是直接或间接继承自View的,所用的所有布局都是直接或间接继承自ViewGroup的,View是Android中最基本的一种UI组件, ...

  3. 百度地图api之创建自定义控件

    百度地图api,创建自定义控件 创建百度地图代码省略,直接上创建控件代码 js里边添加控件代码 /*** 自定义控件*/function addTitle(){function zoomControl ...

  4. 在ASP.NET中创建自定义控件初步(转)

    假如你有大量的asp.net页面,在其中你会要求访问者选择一个邮政编码.然后,基于这个邮编,显示与之相关的城市和省份.这项功能可以通过一个包含邮政编码的dropdownlist控件来组织,或者可以通过 ...

  5. 【WinForm】创建自定义控件(转)

    转自:http://www.cnblogs.com/bomo/archive/2012/12/09/2810559.html 虽然VS为我们提供了很多控件可以使用,但有时候这些控件仍然不能满足我们的要 ...

  6. Android开发系列之创建自定义控件

    Android开发过程中我们经常需要定义自己的控件,一方面基于复用的角度考虑,一方面也是基于逻辑处理思维的角度考虑.在这篇博客里面,笔者想要介绍.总结几种Android自定义控件的方法,如果有什么不对 ...

  7. ios创建自定义控件必须具备的三个方法

    1.当用代码创建控件时调用 -(instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { ...

  8. VS中创建自定义控件

    第一步:创建一个ASP.NET WEB应用程序 第二步:在同一个解决方案中创建一个服务控件项目 2.1 再次创建一个asp.net web应用程序.如图: 2.2 然后在这个项目下创建一个Web窗体服 ...

  9. 用C#开发自定义控件一:创建自定义控件及类库并使用

    CSharp开发自定义控件类库并测试的简单例子 最近想研究一下怎么编写CSharp的自定义控件,在此记录一下学习过程: 首先讲一下我的解决方案工程的组成: 首先打开VS(我的是VS2015)新建一个W ...

最新文章

  1. IDEA一定要懂的32条快捷键
  2. 如何和相亲对象无限聊天?程序员甩了这份架构图……| 每日趣闻
  3. node_modules
  4. 女儿学会走路了,是不是该教她学Python了?
  5. leetcode面试题 16.21. 交换和(二分查找)
  6. 信息学奥赛C++语言:输出判断
  7. 关于页面文件路径的问题
  8. javascript 回车实现 tab 切换功能完美解决
  9. ImageView.ScaleType 属性值
  10. .NET 常用ORM之SubSonic
  11. 爬虫学习之下载韩寒博客
  12. 误差柱状图的三种实现方法
  13. C语言中 1%3,算术什么意思啊 算数什么意思
  14. Java常用工具类-发短信(集成华软通信短信网关)
  15. cad应用技巧:图层特性管理器
  16. 值得和孩子一起看的100部BBC经典纪录片
  17. 蚂蚁算法python_蚁群算法python编程实现
  18. Linux树莓派开发——配置树莓派内核源码,内核编译,更换树莓派Linux内核
  19. 微信公众平台接口调试工具json格式不对怎么搞_腾讯云和微信推出更快速的小程序开发平台,微信读书小程序作示范...
  20. vmware 虚拟机使用windows的 http/socks 代理

热门文章

  1. iPhone 13维修换屏问题:芯片决定面容ID“生死”
  2. 软银在日本推物联网单车共享
  3. 【Docker】7、数据卷详解:具名挂载、匿名挂载、初识DockerFile、数据卷容器
  4. 测试hadoop集群的读写与计算能力
  5. ld --whole-archive 和 --no-whole-archive学习记录
  6. 2021年高处安装、维护、拆除多少钱及高处安装、维护、拆除模拟考试题
  7. P型硅与N型硅的区别
  8. 05-旭日X3派测评——Open Cv Pangolin等库安装ORB_SLAM2 安装运行
  9. 搜藏一个较全的数据集目录
  10. 无人值守安装linux7,CentOS7网络无人值守安装