[置顶] duilib开发基础:创建自定义控件的过程

转载请说明原出处,谢谢~・http://blog.csdn.net/zhuhongshu/article/details/45362751

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

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

       创建自定义控件包括两个进程:
       1、继承现有的控件类创建新的控件类
       2、让程序辨认新的控件并可以在xml中使用


创建新的控件类:

       首先从的现有的Duilib控件当选择1个最适合的控件类,继承他然后重写几个接口。
       我这里拿仿酷狗的换肤窗体中的1个自绘控件做例子(原文地址: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="UIBKImage1small.jpg" bkname="测试" author="Redrain" />
//类名和接口名,在CreateControl函数中会用到
const TCHAR kSkinPickerPictureItemClassName[] = _T("SkinPikerPictureItemUI");
const TCHAR kSkinPickerPictureItemInterface[] = _T("SkinPikerPictureItem");

//黑色的前景图的位置
const TCHAR kSkinPickerPictureItemForeImage[] = _T("file='UILeftTablistitemListBk.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。1般来讲,建立新控件后,最早应当重写的两个函数是GetClass和GetInterface。他们后用来辨别控件的类型的虚函数,用于动态辨认控件类型和做控件的类型转换。

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

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


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

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程序框架已完成,当需要做1些界面的初始操作时可以重写此函数,常见的用法就是在此建立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中的判断字符串1致,比如这里就是SkinPickerPictureItem。这样,在解析xml进程中,当解析到名为SkinPickerPictureItem的标签时,就会创建出CSkinPickerPictureItemUI控件了。

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

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


CControlUI* CDialogBuilder::Create(STRINGorID xml, LPCTSTR type, IDialogBuilderCallback* pCallback,
CPaintManagerUI* pManager, CControlUI* pParent)


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


        这几步都完成后,你的自定义控件就能够被xml布局正确的辨认并创建了。至此,创建自定义控件的基本进程就完成了!如果有不明白的,可以多看看仿酷狗的代码、QQDemo等。


封装Win32控件或Win32子窗口:
       如果要给Duilib,增加1个视频播放控件,1般来讲视频播放库都需要依赖1个子窗口。这时候,就应当自定义个控件,并且封装保护1个子窗口了。

       封装的子窗口有3种:第1种比较简单、单纯封装1个子窗口、让视频库1类的库依赖;第2种麻烦1些、封装子窗口、并且处理子窗口的消息;第3种和第2种类似、封装Win32的控件并且处理他的消息。
      单纯封装子窗口:


      这时候就需要重写我之条件到的DoInit函数和SetVisbile等函数了。首先在自定义控件内声明HWND类型的m_hWnd成员变量来保存子窗体指针。

      在DoInit函数里,调用CreateWindowEx函数,创建1个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子窗口,然后可以把这个窗体句柄用于视频播放等。

 
波比源码 – 精品源码模版分享 | www.bobi11.com
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!

波比源码 » [置顶] duilib开发基础:创建自定义控件的过程

发表评论

Hi, 如果你对这款模板有疑问,可以跟我联系哦!

联系站长
赞助VIP 享更多特权,建议使用 QQ 登录
喜欢我嘛?喜欢就按“ctrl+D”收藏我吧!♡