转载自·http://blog.csdn.net/zhuhongshu/article/details/45362751

用Duilib开发界面时,很多情况下库自带的控件不满足需求,就需要基于Duilib建立自定义控件(自绘新的控件,或者用来封装win32的子窗体,来显示视频、网页等)。

在群里经常会有刚接触Duilib的朋友问题怎么建立自己的自定义控件,或者建立的控件无法正常创建出来。我简单写一篇博客,把创建自定义控件的完整过程,和一些注意事项说明一下。另外说一下如果把win32的子窗体封装为控件,希望能有帮助。

创建自定义控件包含两个过程:

1、继承现有的控件类创建新的控件类

2、让程序识别新的控件并可以在xml中使用

创建新的控件类:

首先从的现有的Duilib控件中选择一个最合适的控件类,继承他然后重写几个接口。

我这里拿仿酷狗的换肤窗体中的一个自绘控件做例子(原文地址:http://blog.csdn.net/zhuhongshu/article/details/38491389)

仿酷狗音乐盒源代码:http://blog.csdn.net/zhuhongshu/article/details/41037875

为了做出换肤控件,首先选择CButtonUI为父类,因为CButtonUI控件本身就已经包含了normal、hot、pushed等状态,同时包含单击事件。

#ifndef SKIN_PICKER_PICTURE_ITEM_H
#define SKIN_PICKER_PICTURE_ITEM_H//xml sample:<SkinPikerPictureItem name="" width="118" height="70" bkimage="UI\BKImage\1small.jpg" bkname="测试" author="Redrain" />
//类名和接口名,在CreateControl函数中会用到
const TCHAR kSkinPickerPictureItemClassName[] =    _T("SkinPikerPictureItemUI");
const TCHAR kSkinPickerPictureItemInterface[] =    _T("SkinPikerPictureItem");//黑色的前景图的位置
const TCHAR kSkinPickerPictureItemForeImage[] =    _T("file='UI\\LeftTab\\listitem\\ListBk.png' fade='150'");//边框的颜色、图片名称的文字颜色、作者信息的文字颜色
const DWORD kBorderColor                      = 0xFF64B0FA;
const DWORD kBkNameColor                      = 0xFFFFFFFF;
const DWORD kAuthorColor                      = 0xFFAAAAAA;class CSkinPikerPictureItemUI : public CButtonUI
{
public:CSkinPikerPictureItemUI();LPCTSTR GetClass() const;LPVOID GetInterface(LPCTSTR pstrName);void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);void PaintStatusImage(HDC hDC);private:CDuiString m_BkName;CDuiString m_Author;
};#endif // SKIN_PICKER_PICTURE_ITEM_H
CSkinPikerPictureItemUI::CSkinPikerPictureItemUI()
{m_Author = _T("作者:");
}
LPCTSTR CSkinPikerPictureItemUI::GetClass() const
{return kSkinPickerPictureItemClassName;
}LPVOID CSkinPikerPictureItemUI::GetInterface(LPCTSTR pstrName)
{if( _tcscmp(pstrName, kSkinPickerPictureItemInterface) == 0 ) return static_cast<CSkinPikerPictureItemUI*>(this);return CButtonUI::GetInterface(pstrName);
}void CSkinPikerPictureItemUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)
{if( _tcscmp(pstrName, _T("bkname")) == 0 ) m_BkName = pstrValue;else if( _tcscmp(pstrName, _T("author")) == 0 ) m_Author += pstrValue;CButtonUI::SetAttribute(pstrName, pstrValue);
}void CSkinPikerPictureItemUI::PaintStatusImage(HDC hDC)
{CButtonUI::PaintStatusImage(hDC);if( IsFocused() ) m_uButtonState |= UISTATE_FOCUSED;else m_uButtonState &= ~ UISTATE_FOCUSED;if( !IsEnabled() ) m_uButtonState |= UISTATE_DISABLED;else m_uButtonState &= ~ UISTATE_DISABLED;if( (m_uButtonState & UISTATE_PUSHED) != 0 || (m_uButtonState & UISTATE_HOT) != 0) {DrawImage(hDC, kSkinPickerPictureItemForeImage) ;//计算作者信息文字和背景图片名字文字的显示位置,这里是用了硬编码,请使用者自己修改RECT rcBkName = m_rcItem;LONG nTextPadding = (m_rcItem.right - m_rcItem.left  - CRenderEngine::GetTextSize(hDC, GetManager(),\m_BkName.GetData(), m_iFont, m_uTextStyle).cx) / 2;rcBkName.left += nTextPadding;rcBkName.right -= nTextPadding;rcBkName.top += 15;rcBkName.bottom = rcBkName.top + 20;RECT rcAuthor = m_rcItem;nTextPadding = (m_rcItem.right - m_rcItem.left - CRenderEngine::GetTextSize(hDC, GetManager(),\m_Author.GetData(), m_iFont, m_uTextStyle).cx) / 2;rcAuthor.left += nTextPadding;rcAuthor.right -= nTextPadding;rcAuthor.top += 40;rcAuthor.bottom = rcAuthor.top + 20;CRenderEngine::DrawText(hDC, m_pManager, rcBkName, m_BkName, kBkNameColor, m_iFont, m_uTextStyle);CRenderEngine::DrawText(hDC, m_pManager, rcAuthor, m_Author, kAuthorColor, m_iFont, m_uTextStyle);CRenderEngine::DrawRect(hDC, m_rcItem, 2, kBorderColor);}}

新的控件名为CSkinPickerPictureItemUI。一般来说,建立新控件后,最先应该重写的两个函数是GetClass和GetInterface。他们后用来区分控件的类型的虚函数,用于动态识别控件类型和做控件的类型转换。

从Duilib的自带控件上可以看出,比如当前的自定义控件类名为CSkinPickerPictureItemUI,那么GetClass函数返回的字符串SkinPickerPictureItemUI。而GetInterface函数是根据传入的参数,是否与自身的字符串匹配,来决定能否把自己转换为需要的控件类型。GetInterface中用来匹配的字符串,应该与xml中的对应的控件的标签名称一直,这里应该是SkinPickerPictureItem。

比如CButtonUI类,GetClass对应ButtonUI,GetInterface对应Button。这不是强制的,但是保持这个风格很重要!

理论上,完成这两个接口就创建好最基本的自定义控件了。但是为了让自定义控件的行为和外观更丰富,就需要重写更多的函数了,我这里把经常会重写的函数说明一下!

 virtual void DoEvent(TEventUI& event);virtual void DoPaint(HDC hDC, const RECT& rcPaint);virtual void PaintBkColor(HDC hDC);virtual void PaintBkImage(HDC hDC);virtual void PaintStatusImage(HDC hDC);virtual void PaintText(HDC hDC);virtual void PaintBorder(HDC hDC);virtual void DoInit();virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);virtual bool IsVisible() const;virtual void SetVisible(bool bVisible = true);virtual void SetInternVisible(bool bVisible = true); // 仅供内部调用,有些UI拥有窗口句柄,需要重写此函数virtual void SetPos(RECT rc);

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

DoEvent函数:控件的核心函数,他是消息处理函数,用来处理Duilib封装过的各个消息,比如鼠标的移入移出、出现的悬停、单击双击、右击、滚轮滑动、获取焦点、设置光标等等。所以如果你的控件需要修改这些行为,必须重写这个函数,具体的处理方法可以参考Duilib现有的控件或者仿酷狗程序。

DoPaint函数:控件的核心函数,他是控件的绘制处理函数,当Duilib底层要重新绘制这个控件,或者控件自己调用Invalidata函数强制自己刷新时,这个函数就会被触发,在这个函数里完成了各种状态下的背景前景绘制,背景色绘制,文本绘制,边框绘制。而这个函数会调用PaintBkColor、PaintBkImage、PaintStatusImage、PaintText、PaintBorder等函数来完成各个绘制步骤。所以你可以根据需求来决定重写DoPaint或者只重写某几个PaintXXX函数。DoPaint函数经常和DoEvent函数结合使用,DoEvent得到了某个消息后,改变控件的状态,然后调用Invalidate函数让控件重绘。

SetAttribute函数:用于扩展自定义控件的属性,Duilib的控件本身已经包含name、text、bkimage等属性,如果要增加新属性,就需要重写此函数来扩展属性,上面的CSkinPickerPictureItemUI例子中已经有用法了。

DoInit函数:当控件被添加到容器后,由容器调用的函数。在这里,整个Duilib程序框架已经完成,当需要做一些界面的初始操作时可以重写此函数,常见的用法就是在此建立Win32子窗体并且封装它,相关内容我在后面再说。

IsVisible、SetVisible、SetInternelVisible、SetPos:这几个函数同样也是,当控件封装了Win32子窗口后,重写这几个函数来控制子窗口的显示和隐藏、和位置。

这样就创建完成了自定义控件。

识别新控件:

自定义控件创建完毕后,需要做的就是让控件可以被xml布局识别出来。为此我们需要完成Duilib的IDialogBuilderCallback接口,重写这个接口中的CreateControl函数。

通常情况下,可以让窗体类继承IDialogBuilderCallback接口并且重写CreateControl(DuiLib自带的WindowImplBase窗体类已经继承了这个接口,如果是继承WindowImplBase的话就直接重写CreateControl就可以了)。函数处理方法是比较传入的字符串,根据字符串来决定返回什么控件的指针,这个传入的字符串就是xml文件中控件的标签,比如<Button />中的字符串Button。

CControlUI* CSkinPickerDialog::CreateControl(LPCTSTR pstrClass)
{if (_tcsicmp(pstrClass, kSkinPickerPictureItemInterface) == 0)return new CSkinPikerPictureItemUI();return NULL;
}

习惯上,在xml中自定义控件的标签名称应该和控件的GetInterface中的判断字符串一致,比如这里就是SkinPickerPictureItem。这样,在解析xml过程中,当解析到名为SkinPickerPictureItem的标签时,就会创建出CSkinPickerPictureItemUI控件了。

实际上,谁来继承IDialogBuilderCallback接口肯定都可以,比如QQDemo和仿酷狗里面,都给自定义控件本身继承了这个接口。

当程序响应WM_CREATE消息时,会建立一个CDialogBuilder对象,并且调用他的Create方法来解析xml文件。

CControlUI* CDialogBuilder::Create(STRINGorID xml, LPCTSTR type, IDialogBuilderCallback* pCallback, 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保存句柄。比如:

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内核浏览器控件代码。不过与之不同的是,我们需要重写两个函数

LPCTSTR GetWindowClassName() const;LPCTSTR GetSuperClassName() const;

这里最主要的就是处理GetSuperClassName函数,这个函数的作用就是超类化,而封装子窗口并处理消息是子类化,这两个操作恰好相反。在 GetSuperClassName函数内,要范围Win32控件对应的类名、Duilib检测到GetSuperClassName函数函数后就会创建Win32控件。这时我们处理HandleMessage函数,就可以处理到Win32控件的消息的。具体的处理逻辑请参见CEditUI控件。

总结:

差不多就说道这里了,把常见的自定义控件的基本步骤说明了一下,实际开发时还要多看Duilib的源码,才能称心如意的开发控件,希望对刚接触Duilib的朋友有帮助!

duilib开发基础:创建自定义控件的过程相关推荐

  1. os-cocos2d游戏开发基础-进度条-开发笔记

     os-cocos2d游戏开发基础-进度条-开发笔记(十)   ios-cocos2d游戏开发基础-游戏音效-开发笔记(九)       ios-cocos2d游戏开发基础-CCLayer和Touch ...

  2. asp.net控件开发基础系列

    本系列文章示例源码下载.各位如遇问题,请多查msdn,多利用网络.本人可能没时间一一回复,谢谢你们的支持,希望看到此文的人都能学好控件开发 http://www.cnblogs.com/Clingin ...

  3. duilib创建自定义控件

    我之前也写过一片封装xml为一个容器的文章,只是写的很随意,仅仅贴出了一个demo的地址. 在群里还有一些刚刚接触duilib的朋友们问到duilib自定义控件的问题,这里我转载一篇redrain大佬 ...

  4. 【MC 网易-我的世界-mod开发基础笔记】 --- 创建第一个空白Mod

    目录 创建Mod文件夹 构建Mod的基本目录结构 构建行为包基本结构 构建脚本文件夹基本结构 构建 mod 启动入口脚本 构建资源包基本结构 构建国际化文件基本结构 自定义物品配置中文名称 自定义方块 ...

  5. 一.Duilib开发之基本使用

    一. Duilib介绍 Duilib是一款轻量级,遵循BSD协议的开源C++ GUI框架,可以免费用于商业项目,是由杭州月牙儿网络技术有限公司基于DirectUI界面思想设计出来的GUI开源框架:所谓 ...

  6. 虚幻引擎编辑器开发基础(一)

    虚幻引擎编辑器开发基础(一) 文章目录 虚幻引擎编辑器开发基础(一) 一.前言 二.插件与模块 2.1 插件(Plguin) 2.1.1 插件的作用 2.1.2 插件的类型 2.1.3 插件结构 2. ...

  7. Asp.net控件开发学习笔记(三)-控件开发基础

    封装      在asp.net中,控件被分为两类.用户控件和自定义服务器控件.前者就是我们经常用来将一些可复用的内容封装成的.ascx文件.这里主要研究后者. 创建自定义服务器控件      创建自 ...

  8. ASP.NET Web API 过滤器创建、执行过程(二)

    ASP.NET Web API 过滤器创建.执行过程(二) 前言 前面一篇中讲解了过滤器执行之前的创建,通过实现IFilterProvider注册到当前的HttpConfiguration里的服务容器 ...

  9. IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

    1.前言 众所周之,IM是个典型的快速数据流交换系统,当今主流IM系统(尤其移动端IM)的数据流交换方式都是Http短连接+TCP或UDP长连接来实现.Http短连接主要用于从服务器读取各种持久化信息 ...

  10. c# 无法加载oraops.dll_Robotstudio软件二次开发:基于C#语言的Smart组件开发基础

    Robotstudio软件除了支持Add-Ins插件的二次开发以外,还支持Smart组件的二次开发.开发语言同样是基于.NET框架的C#语言或VB语言.Smart组件是Robotstudio软件中实现 ...

最新文章

  1. 【紧急】支付宝全面停止微信合作
  2. 不一样的STAR法则
  3. 如何通过自学找到一份开发的工作?
  4. PS教程第二十二课:羽化选区
  5. leetcode976. 三角形的最大周长(又是你得不到的简单题)
  6. websocket一直无法链接_.NET Core 实现基于Websocket的在线聊天室
  7. 基于知识库的自动问答:seq2seq模型实践
  8. antimalware service executable占用内存_解决 vue 项目运行过程中内存泄漏问题
  9. xmemecached中的一致性hash算法
  10. 小程序 字号设置 slider滚动改变大小_Snipaste(滚动截图软件)app下载|Snipaste(滚动截图软件) 1.15.2 绿色版(32/64位)...
  11. thinkphp6+vue前后端分离开发验证码总是验证不正确问题
  12. excel锁定前几行,无法选择和编辑
  13. 简介响应状态码1xx、2xx、5xx
  14. UI(六) - 如何架构UI框架
  15. Hibernate实战——双向N-N关联
  16. 【重磅整理】提前看287篇ICLR-2021 深度强化学习领域论文得分汇总列表
  17. java判断string是数字_(转)java判断string变量是否是数字的六种方法小结
  18. 什么是服务器丢包?恒讯科技教你如何解决
  19. 题目:一球从100米高度自由落下,每次落地后反跳回原高度的一半再落下, 求它在第10次落地时,共经过多少米
  20. 华为推送没有跳转到指定页面

热门文章

  1. python数据类型有哪些、分别有什么用途_python数据类型
  2. 二叉树的高度_完全二叉树的子节点数
  3. 不能将紧实的字段 绑定到_紫光芯城市政配套建设项目样板段预计下月完成
  4. docker 时区_腾讯云上用Docker建立Kiftd服务器
  5. idea新建一个springboot项目_SpringBoot(二):第一个Spring Boot项目
  6. java multibox_月光软件站 - 编程文档 - Java - Struts 中 Multibox 的用法
  7. Introduction to Computer Networking学习笔记(九):error detection 错误探查 Checksum、CRC、MAC
  8. 基于adaboost算法的人脸检测_基于噪声检测的图像去噪算法
  9. Java的switch case 语句
  10. 同一个按钮,实现排序