Duilib 创建自定义控件

  用Duilib开发界面时,很多情况下库自带的控件不满足需求,就需要基于Duilib建立自定义控件(自绘新的控件,或者用来封装win32的子窗体,来显示视频、网页等)。  
   在群里经常会有刚接触Duilib的朋友问题怎么建立自己的自定义控件,或者建立的控件无法正常创建出来。我简单写一篇博客,把创建自定义控件的完整过程,和一些注意事项说明一下。另外说一下如果把win32的子窗体封装为控件,希望能有帮助。  

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

class ComputerExamineUI : public CContainerUI //1.创建一定要的控件
{
public:
	ComputerExamineUI()
	{
		CDialogBuilder builder;//2.自定义控件绑定xml文件
		CContainerUI* pComputerExamine = static_cast(builder.Create(_T("ComputerExamine.xml"), (UINT)0));
		if( pComputerExamine ) {//3.添加xml绘制控件到自定义控件
			this->Add(pComputerExamine);
		}
		else {
			this->RemoveAll();
			return;
		}
	}
};
class CDialogBuilderCallbackEx : public IDialogBuilderCallback
{//4.让程序识别出自定义控件
public:
	CControlUI* CreateControl(LPCTSTR pstrClass) 
	{
		if( _tcscmp(pstrClass, _T("ComputerExamine")) == 0 ) 
			return new ComputerExamineUI;
		return NULL;
	}
};
   新的控件名为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中自定义控件的标签名称应该和控件的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等。  

LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
LONG styleValue = ::GetWindowLong(*this, GWL_STYLE);
styleValue &= ~WS_CAPTION;
::SetWindowLong(*this, GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
m_pm.Init(m_hWnd);
CDialogBuilder builder;
CDialogBuilderCallbackEx cb;
CControlUI* pRoot = builder.Create(_T("skin.xml"), (UINT)0,  &cb, &m_pm);

ASSERT(pRoot && "Failed to parse XML");
m_pm.AttachDialog(pRoot);
m_pm.AddNotifier(this);
Init();
return 0;
}

你可能感兴趣的:(duilib)