Duilib源码的关键部分

Skilla使用duilib已经有一年了,经过一年的摸索,也逐渐地解开了里面的大大小小的秘密。从熟悉Demo到布局特性的了解也是经历了数月的时间,核心机制也是最后才弄明白的,源码的探索也是由表及里的。但是这个速度是非常缓慢的,所以今天Skilla要写这篇文章,让大家可以有主到次地来认识Duilib。

其实,要想以最快的速度把源码弄通,是需要有先后顺序的,说的再简单点就是要抓住核心,核心理解以后细节部分的难题都会迎刃而解。下面以伪代码的形式来分析一下Duilib的源码。

首先,第一个要说的就是CControlUI,这个是我们使用最频繁的,xml里面的每一个标签都会生成一个CControlUI或它的子类对象。

class CControlUI
{
	void Event(TEventUI& event)    //事件回调
	{
		DoEvent(event);
	}
	virtual void DoPaint()    //控件的绘制代码
	{
		 //按需求去绘制
		CRenderEngine::DrawImage(bkimage,selfRect);
        ...
	}

	virtual void DoEvent(TEventUI& event)
	{
		//控件的事件处理(根据需求修改控件属性)
		if (event.Type==UIEVENT_BUTTONDOWN)
		{

		}else if (event.Type==UIEVENT_BUTTONUP)
		{

		}else if (event.Type==UIEVENT_KEYDOWN)
		{
		}

	}

	void SetWidth(nWidth)
	{
		width = nWidth;    //修改控件属性
		Invalidate();         //刷新
	}
protected:
	//位置属性
	int width;
	int height;
	...

	//图形属性
	String bkimage;
	String bkcolor;
    ...
};

CControlUI类拥有的属性很多,比如位置、图形、文本等等,并且都有Get和Set,Get方法一般是直接返回属性值,Set方法一般是先给属性赋值然后Invalidate刷新。还有两个核心的方法是DoEvent和DoPaint,前者是事件回调(类似于窗口过程函数,由owner窗口过程函数消息处理时调用),后者为渲染回调(由owner窗口过程函数里的WM_PAINT消息处理时调用)。控件的各种属性根据需求抽象出来的,这点很容易理解,但是DoEvent和DoPaint这两个东西是干嘛的?为什么要有这两个东西?这两个函数是设计者抽象出来的,为了和Owner窗口交互的。控件是设计者设计出来的,是不会响应任何windows事件的,为了让CControlUI能响应各种操作(包括鼠标,键盘等等),需要和窗口有一个沟通的桥梁,但是DoEvent和DoPaint就是干这个用的,一个相当于“输入设备”用来响应操作;另一个相当于“输出设备”用来展现视图。

一个窗口的控件也可谓“芸芸众生”,大的套小的,千奇百怪。那么当窗口消息到来时,是如何决定哪个控件去响应事件,哪个控件去刷新视图呢?下面我们来认识一下窗口绘制管理器CPaintManagerUI,来看伪代码。

class CPaintManagerUI
{
public:
	 bool MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
	 {
		 switch(uMsg)
		 {
		 case WM_SIZE:                 //窗口相关消息
			 //去操作被管理的窗口
			 break;
		 case WM_LBUTTONDOWN:   //鼠标类消息(通过鼠标坐标点来确认哪个控件响应)
			 //根据lParam获取到鼠标消息的坐标点
			 pt = lParam;
			CControlUI* pControl = FindControl(pt); //根据坐标点找到控件
			TEventUI event;                                //拟定点击事件
			event.pSender = pControl;
			event.ptMouse = pt;
			event.Type = UIEVENT_BUTTONDOWN;
		    pControl->Event(event);                      //控件去响应事件

			m_pFocus = pControl;                          //当前控件因为被点击了,接受焦点
			TEventUI event;                                //拟定设置焦点事件
			event.pSender = pControl;
			event.ptMouse = pt;
			event.Type = UIEVENT_SETFOCUS;
			 pControl->Event(event);
				 break;

		 case  WM_KEYDOWN:      //键盘类消息(通过焦点来确定哪个控件响应)

			 TEventUI event;                             //拟定键盘事件
			 event.pSender = m_pFocus;
			 ...
			 m_pFocus->DoEvent(event);       //当前拥有焦点的控件响应键盘事件
			 break;
          ...

		 case WM_PAINT:
             //需要刷新的控件去刷新
			 needUpdateControl->DoPaint();

			 break;

		 }
	 }
protected:
	//窗口绘图属性
	HWND m_hWndPaint;
	int m_nOpacity;
	HDC m_hDcPaint;
	HDC m_hDcOffscreen;
	HDC m_hDcBackground;

	//控件树
	CControlUI* m_pRoot;
	//响应事件后的控件
	CControlUI* m_pFocus;
	CControlUI* m_pEventHover;
	CControlUI* m_pEventClick;
	CControlUI* m_pEventKey;
};

CPaintManagerUI的属性和方法也非常多,还是抽出最核心的部分来看,那就是MessageHandler方法。之所以把MessageHandler抽取出来分析,那就是因为它的重要性了,它是挂接窗口消息与控件事件的桥梁。CPaintManagerUI::MessageHandler是窗口消息的集中处理器同样也是所有控件事件的事件源,相当于人体的“神经中枢”。MessageHandler里面处理的消息一共分一下几类:1.鼠标类消息,这类消息一般都有鼠标产生,比如WM_LBUTTONDOWN,WM_MOUSEMOVE等等,鼠标消息转化为控件事件要根据鼠标消息到来时的坐标点,根据这个坐标点在哪个控件的区域内,来决定这个消息属于哪个控件,就像天上掉钱一样,掉到谁家是谁的。找到这个控件后创建一个事件对象,调用该控件的DoEvent方法。2.键盘类消息,这类消息一般由键盘产生,像WM_KEYDOWN这一类的。键盘消息到来时如何确认属于哪个控件呢?就像Windows原生的窗口一样,虽然是虚拟的控件,但同样需要焦点。键盘消息到来时要根据焦点来确定哪个控件拥有该消息,当前焦点在哪个控件上,哪个控件才响应键盘消息。3.渲染消息,指的就是WM_PAINT消息,当有控件需要刷新时,需要刷新的控件调用自身的DoPaint消息。当然这是简单的说法,事实上里面的处理也是很复杂的,时间关系这里就不介绍了。

下面看一下在使用时是怎样的,还是看伪代码

class MyWindow :public CWindowWnd
{
public:
	HandleMessage()
	{
	     m_PaintManager.MessageHandler();
	}

protected:
	CPaintManagerUI m_PaintManager;

};

使用时就相对简单了,把窗口消息交给m_PaintManager,由MessageHandler转换为控件事件,再由控件去处理相应的操作。

上面所说的就是Duilib里面最关键的代码,理解这个就可以根据需求去派生自己的控件了。像MessageHandler里面没有处理到的消息,比如WM_IME_STARTCOMPOSITION(这个是输入法相关的,属于键盘类消息)可以自己加上,当然同样也要添加相应的控件事件UIEVENT_IME_STARTCOMPOSITION,起什么名字不重要,关键是要区分开。

原理固然如此,但是细节决定成败。一些细节性问题,比如控件是如何创建的、xml是如何解析的、Findcontrol是如何实现的、绘图是如何实现的同样要花时间去研究。

选择Duilib就是因为它的灵活,做出的产品非常细腻。所以读懂源码还是必须的,如果仅仅是停留在使用的层面,远不如去选择一款非开源的界面库,但是拿有限的方法去实现无限的需求基本上是不可能的。

今天就到这里了,如有问题或建议请联系作者:Skilla(QQ:848861075)

时间: 2024-11-04 00:33:01

Duilib源码的关键部分的相关文章

Duilib源码分析(一)整体框架

Duilib界面库是一款由杭州月牙儿网络技术有限公司开发的界面开源库,以viksoe项目下的UiLib库的基础上开发(此后也将对UiLib库进行源码分析):通过XML布局界面,将用户界面和处理逻辑彻底分离,极大地提高用户界面的开发效率.一般常用于开发小型项目Windows桌面客户端软件:其子窗口不以窗口句柄的形式创建,只是逻辑上的窗口,绘制在父窗口之上.目前开源协议以BSD发布,可使用于商业应用,好了,其他更为详细的介绍,请查阅其官网或百度. 源码获取: 目前duilib不在被维护,基本上网络中

Duilib源码分析(五)UI布局—Layout

接下来,继续分析duilib之UI布局Layout,目前提供的布局有:VerticalLayout.HorizontalLayout.TileLayout.TabLayout.ChildLayout分别为垂直布局.水平布局.平铺布局.TAB布局.子窗体布局: 一般项目中用得比较多的是垂直布局.水平布局,我们将分别分析各布局: VerticalLayout:垂直布局,继承于CContainerUI容器UI类:而CContainerUI也继承于CControlUI,故VerticalLayout实际

DuiLib 源码分析之解析xml类CMarkup & CMarkupNode 头文件

xml使用的还是比较多的,duilib界面也是通过xml配置实现的 duilib提供了CMarkkup和CMarkupNode类解析xml,使用起来也是比较方便的,比较好奇它是怎么实现的,如果自己来写一个 解析又需要怎样架构,架构之路还很遥远... 先来看看头文件吧,CMarkup主要是用于分割xml,判断xml格式是否正确:CMarkupNode主要是将CMarkup分割的xml,获取节点中的属性, 最多支持64个属性 1 enum 2 { 3 XMLFILE_ENCODING_UTF8 =

Duilib源码分析(四)绘制管理器—CPaintManagerUI

接下来,分析uilib.h中的UIManager.h,在正式分析CPaintManagerUI前先了解前面的一些宏.结构: 枚举类型EVENTTYPE_UI:定义了UIManager.h中事件通告类型TEventUI结构中的各Type值,从UIEVENT__FIRST至UIEVENT__LAST分别定义了  键盘按键事件 (UIEVENT__KEYBEGIN~UIEVENT__KEYEND).鼠标事件(UIEVENT__MOUSEBEGIN~UIEVENT__MOUSEEND).以及其他的几个事

Duilib源码分析(四)绘制管理器—CPaintManagerUI—(前期准备二)

接下来,我们继续分析UIlib.h文件中余下的文件,当然部分文件可能顺序错开分析,这样便于从简单到复杂的整个过程的里面,而避免一开始就出现各种不理解的地方. UIManager.h:UI管理器,暂时放在后面介绍: UIBase.h:UI窗口相关,包括常用的窗口风格.窗口类风格的宏定义,调试相关,以及基本窗口类: 首先宏定义了几个常用的窗口风格.窗口扩展风格和窗口类风格,以UI_WNDSTYLE_XXX和UI_CLASSSTYLE__XXX开头的: ASSERT采用的是CRT的_ASSERTE.D

Duilib源码分析(三)XML解析器—CMarkup

上一节介绍了控件构造器CDialogBuilder,接下来将分析其XML解析器CMarkup: CMarkup:xml解析器,目前内置支持三种编码格式:UTF8.UNICODE.ASNI:CMarkupNode:xml节点类 先介绍CMarkup: XMLELEMENT: xml节点元素类型定义,iStart,节点元素在xml文件中的起始位置:iChild,节点元素子节点:iNext,节点元素的下一个节点(兄弟节点):iParent,节点元素的父节点:iData, 节点元素的数据. CMarku

Duilib源码分析(四)绘制管理器—CPaintManagerUI—(前期准备三)

接下来,我们将继续分析UIlib.h文件中其他的文件, UIContainer.h, UIRender.h, WinImplBase.h, UIManager.h,以及其他布局.控件等: 1. UIRender.h:UI渲染器,其中cpp文件中,定义的ZIP压缩相关的数据结构,以及宏操作,与XUnzip.cpp中一样的(个人认为可以提取出来作为共用的一部分),此外还有stbi_load_from_memory. stbi_image_free,涉及到图片加载操作,具体详细细节可参考stb_ima

DuiLib 源码分析之解析xml类CMarkup & CMarkupNode cpp文件

时隔5个月才有时间接着写未完成的实现部分,也是惭愧呀 选几个关机的函数来解析,一些get方法就忽略掉吧 CMarkupNode 与 CMarkUp 互为友元类,CMarkUp 实现解析,CMarkupNode 用于存储读取节点数据 1 void CMarkupNode::_MapAttributes() 2 { 3 m_nAttributes = 0; 4 LPCTSTR pstr = m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iS

Android事件传递机制详解及最新源码分析——ViewGroup篇

在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴,强烈建议先阅读上一篇. 好了,废话还是少说,直奔主题,开始本篇的ViewGroup事件传递机制探索之旅. 依然从简单的Demo例子现象开始分析 新建安卓工程,首先自定义一个Button以及一个RelativeLayout,很简单,只是重写了主要与事件传递机制相关的方法,代码如下: 自定义WLButton类: 1 public class WLButton e