Duilib学习笔记(一)

    xiaoxiao2023-03-24  4

     从网上下载的duilib工程中附带的一副总体设计图(如下所示),可以先整体了解一下,有个初步的认识,对后续进一步深入了解学习会很有帮助。

    通过设计图有了一个初步认识后,接下来开始进一步深入学习了解,主要从以下几个方面进行了解学习:

    库的组成;框架基本流程;元素创建机制;消息处理机制。

    1. 库的基本组成

    1.1 工具库

    由于duilib没有对外部的任何库进行依赖,所以在其内部实现了很多用于支撑项目的基础类(如下图所示)。这些类分布在Util文件夹中: UI相关:CPoint / CSize / CDuiRect 简单容器:CStdPtrArray / CStdValArray / CStdString / CStdStringPtrMap

    上面这些类看名字就基本能够理解其具体的含义了,当然除了基本的基础库,还有一些和窗口使用相关的工具的封装,如窗口工具:WindowImplBase,这个工具我们在这里不详述,后面使用中会经常用到。

    1.2 控件库

    控件库在duilib的实现中被分为了两块:Core和Control:

    Core中包含的是所有控件公用的部分,里面主要是一些基类和绘制的封装。 Control中包含的就是各个不同的控件的行为了。

    这当中尤其要注意控件基类CControlUI和容器基类CContainerUI,这是duilib核心类(如下图所示)中是很重要的两部分:

    1.2.1. 控件基类:CControlUI

            CControlUI在整个控件体系中非常重要,它是所有控件的基类,也是组成控件树的基本元素,控件树中所有的节点都是一个CControlUI。         它基本包括了所有控件公共的属性,如:位置,大小,颜色,是否有焦点,是否被启用等等。当然这个类中还提供了非常多的基础函数,用于重载来实现子控件,如获取控件名称和ClassName,是否显示等等。         另外为了方便从XML中直接解析出控件的各个属性,这个类中还在提供了一个SetAttribute的方法,传入字符串的属性名称和值对特定的属性进行设置,内部其实就是挨个比较字符串去完成的,所以平时使用的时候就还是不要使用的比较好了,因为每个属性实际上都有特定的方法来获取和设置。         另外每个控件中还有几个事件管理的对象——CEventSource,这些对象会在特定的时机被触发,如OnInit,调用其中保存的各个回调函数。

    1.2.2. 容器基类:CContainerUI

            有了基本的控件基类之后,我们就需要容器来将他管理起来,这个容器就是CContainerUI,其内部用一个数组来保存所有的CControlUI的对象,后续的所有工作,就都是基于这个对象来进行的了。 这样在CContainerUI里面,主要实现了一下几个功能:

    子控件的查找:CContainerUI::FindControl 子控件的生命周期管理:是否销毁(在Remove的时候自动销毁) / 是否延迟销毁(交给CPaintMangerUI去一起销毁)。 滚动条:所有的容器都支持滚动条,在其内部会对键盘和鼠标滚轮事件进行处理(CContainerUI::DoEvent),对其内部所有的元素调整位置,最后在绘制的时候实现滚动的效果 绘制:由于容器中有很多元素,所以为了加快容器的绘制,绘制的时候会获取其真正需要绘制的区域,如果子控件不在此区域中,那么就不予绘制了

    而对于这些控件的绘制实现以及相关使用,在后续具体进一步学习中再深入详解。

    2. 框架基本流程

    框架的基本流程实际上类似Win32创建窗口流程,如果对于Win32比较熟悉,这部分可以很快掌握。

    [cpp] view plain copy print ? intAPIENTRY WinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow)  {      CPaintManagerUI::SetInstance(hInstance);       // 第一步: 实例句柄与渲染类关联      CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T(”skin”));            HRESULTHr = ::CoInitialize(NULL);              // 第二步:初始化COM库, 为加载COM库提供支持      if( FAILED(Hr) ) return0;        CMainFrameWnd* pFrame = newCMainFrameWnd();   // 第三步:创建窗口类      if( pFrame == NULL ) return0;        pFrame->Create(NULL, _T(”主程序”), UI_WNDSTYLE_FRAME, 0L, 0, 0, 800, 600); // 第四步:注册窗口类与创建窗口      //  实际上这里调用Create操作和Win32创建窗体一样,内部实际上做了以下操作:      //  -> RegisterSuperclass (注册一个超类 即已有一个窗口类的基上再注册一个窗口类)      //  -> RegisterWindowClass (注册窗口类)      //  -> ::CreateWindowEx (创建窗口,此时触发 WM_CREATE 消息)      // -> HandleMessage  ( WM_CREATE消息处理OnCreate)        pFrame->CenterWindow();         // 第五步:窗口居中显示      ::ShowWindow(*pFrame, SW_SHOW);        CPaintManagerUI::MessageLoop();// 第六步:处理消息循环        ::CoUninitialize();            // 第七部:退出程序并释放COM库        return0;  }   intAPIENTRY WinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow) { CPaintManagerUI::SetInstance(hInstance); // 第一步: 实例句柄与渲染类关联 CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T("skin")); HRESULTHr = ::CoInitialize(NULL); // 第二步:初始化COM库, 为加载COM库提供支持 if( FAILED(Hr) ) return0; CMainFrameWnd* pFrame = newCMainFrameWnd(); // 第三步:创建窗口类 if( pFrame == NULL ) return0; pFrame->Create(NULL, _T("主程序"), UI_WNDSTYLE_FRAME, 0L, 0, 0, 800, 600); // 第四步:注册窗口类与创建窗口 // 实际上这里调用Create操作和Win32创建窗体一样,内部实际上做了以下操作: // -> RegisterSuperclass (注册一个超类 即已有一个窗口类的基上再注册一个窗口类) // -> RegisterWindowClass (注册窗口类) // -> ::CreateWindowEx (创建窗口,此时触发 WM_CREATE 消息) // -> HandleMessage ( WM_CREATE消息处理OnCreate) pFrame->CenterWindow(); // 第五步:窗口居中显示 ::ShowWindow(*pFrame, SW_SHOW); CPaintManagerUI::MessageLoop();// 第六步:处理消息循环 ::CoUninitialize(); // 第七部:退出程序并释放COM库 return0; }

    3.元素创建机制

    第一步:响应WM_CREATE消息;

    第二步:主窗口类与窗口句柄关联

    [cpp] view plain copy print ? m_pm.Init(m_hWnd)   m_pm.Init(m_hWnd)

    第三步:加载XML并动态创建界面无素,与布局界面元素            

    [cpp] view plain copy print ? CDialogBuilder builder;  CDialogBuilderCallbackEx cb;  CControlUI* pRoot = builder.Create(_T(”skin.xml”), (UINT)0,  &cb, &m_pm);   CDialogBuilder builder; CDialogBuilderCallbackEx cb; CControlUI* pRoot = builder.Create(_T("skin.xml"), (UINT)0, &cb, &m_pm);

    第四步:附加控件到HASH表      

    [cpp] view plain copy print ? PaintManagerUI::AttachDialog      InitControls          FindControl               __FindControlFromNameHash               pManager->m_mNameHash.Insert   PaintManagerUI::AttachDialog InitControls FindControl __FindControlFromNameHash pManager->m_mNameHash.Insert

    第五步:添加通知处理

    [cpp] view plain copy print ? CPaintManagerUI::AddNotifier   CPaintManagerUI::AddNotifier

    第六步:窗口的绘制(以上是窗口的创建过程,通过xml,所有控件都被加载到CPaintManagerUI)

    [cpp] view plain copy print ? CPaintManagerUI响应WM_PAINT消息,开始双缓存绘图    m_pRoot->DoPaint绘背景图     CControlUI::DoPaint    CRenderEngine 真正的绘图类    pPostPaintControl->DoPostPaint 在背景图上绘制控件    ::BitBlt 把离屏视图画到主屏上   CPaintManagerUI响应WM_PAINT消息,开始双缓存绘图 m_pRoot->DoPaint绘背景图 CControlUI::DoPaint CRenderEngine 真正的绘图类 pPostPaintControl->DoPostPaint 在背景图上绘制控件 ::BitBlt 把离屏视图画到主屏上

    4.消息处理机制

     第一步:注册消息处理函数

    在CWindowWnd注册窗口(RegisterWindowClass())里,注册消息回调函数(__WndProc);

    第二步:消息分发

            消息回调函数(处理所有系统发送的消息),然后回调函数通过子类的CMainFrameWnd::HandleMessage对消息进行分发。

           非窗口消息通过CMainFrameWnd::HandleMessage调用CPaintManagerUI::MessageHandler进行分发。

    第三步:消息循环

          在CPaintManagerUI类的MessageLoop处理消息循环; 

          接收到消息以后,进入消息回调函数(__WndProc);

    (注:以下内容以鼠标单机Button事件为例)

    第四步:处理控件消息

            鼠标按下时(WM_LBUTTONDOWN),查找鼠标点击的控件。

            处理控件的鼠标按下消息:通过调用基类CControlUI:: DoEvent,引起子类如CButtonUI::DoEvent事件。

           子类的DoEvent对不同类型的事件进行处理。通过CPaintManagerUI:: SendNotify回调控件注册的事件。

    PS: 关注公众号,技术分享,学习交流 

    转载请注明原文地址: https://ju.6miu.com/read-1202558.html
    最新回复(0)