duilib基本框架
最近我一个同学在项目中使用到了duilib框架,但是之前并没有接触过,他与我讨论这方面的内容,看着官方给出的精美的例子,我对这个库有了很大的兴趣,我自己也是初学这个东东,我在网上花了不少时间来找相关的资料,但是找到的不多,官方给的文档又不全面,但是我还是找到了一些博主贡献的优秀的博文,现在我是通过博文上的讲解加上自己查看源代码的一些心得,正在艰难的前行。现在正在看的是博主Alberl在博客园中的duilib基础教程中的内容,下面的代码都是在他博客中给出代码的基础上做了一点小小的修改。点击这里跳转到对应的博客,以及博主夜雨無聲的博客,博客地址
duilib的简介
国内首个开源 的directui 界面库,它提供了一个所见即所得的开发工具——UIDesigner,它只有主框架窗口,其余的空间全部采用绘制的方式实现,所以对于控件来说没有句柄和窗口类等内容,它通过UIDesigner工具将用户定义的窗口保存在xml文件中,在创建窗口时读取xml文件中的内容,来绘制相应的控件。目前有许多界面采用duilib编写,大家可以去网上搜集相关资料。
环境的配置
首先我们去github上获取相关的源代码,这个是对应的项目地址:https://github.com/duilib/duilib
下载完后,在目录中找到一个.sln结尾的文件,使用visual studio编译器打开,打开后发现有一个duilib的项目,以及其他,其实真正有用的就是这个duilib,其余的都是官方给出的例子代码。一般只需要编译这个duilib项目就可以了,当初没注意直接点了编译全部的,结果报了一堆错误,其实都是没有对应的lib和dll文件造成的。在VS环境下有一个编译选项,如下图所示
上面有4个编译选项,最好将所有的都编译一遍,这样在对应项目的bin目录下会生成四个dll文件,这几个文件分别是debug下的UNICODE编码文件、ANSI文件以及Release版本下的UNICODE编码文件、ANSI文件。u代表unicode d代表debug。另外在lib目录下会生成对应的lib文件。
在新建的工程中,点击属性在属性对话框中选择VC++目录,在源文件,库文件,包含文件中将对应的路径添加进去,分别是项目目录和lib文件目录。如下图(刚开始有点问题所以添加的内容有点多,但是不影响正常使用):
最后可以在环境变量的Path变量中添加对应的dll路径,这样就不需要将dll文件拷贝到自己项目的exe文件所在位置处。
其实上述的环境可以不用设置,如果不设置,在编写程序包含相关路径时就需要给定完整的路径。
到此处为止,整个开发环境就已经搭建好了,剩下的就是代码的编写了。
基本的框架窗口
首先新建一个Win32类型的项目,添加主函数。然后创建一个新类,我们叫做CDuiFrameWnd,下面是类的源代码
//头文件
#include <DuiLib\UIlib.h>
using namespace DuiLib;
class CDuiFrameWnd :
public CWindowWnd
{
public:
CDuiFrameWnd();
~CDuiFrameWnd();
virtual LPCTSTR GetWindowClassName() const;
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
protected:
CPaintManagerUI m_PaintManager;
};
//cpp文件
LPCTSTR CDuiFrameWnd::GetWindowClassName() const
{
return _T("DuiFrameWnd");
}
LRESULT CDuiFrameWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
if (WM_CLOSE == uMsg)
{
::CloseWindow(m_hWnd);
::DestroyWindow(m_hWnd);
}
if (WM_DESTROY == uMsg)
{
::PostQuitMessage(0);
}
return __super::HandleMessage(uMsg, wParam, lParam);
}
为了能够使用对应的dll文件,还需要引入对应的lib文件,我们在公共的头文件中加入如下代码
#ifdef _DEBUG
# ifdef _UNICODE
# pragma comment(lib, "Duilib_ud.lib")
# else
# pragma comment(lib, "Duilib_d.lib")
# endif
#else
# ifdef _UNICODE
# pragma comment(lib, "Duilib_u.lib")
# else
# pragma comment(lib, "Duilib.lib")
# endif
#endif
在主函数中的代码如下:
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
CPaintManagerUI::SetInstance(hInstance);
CDuiFrameWnd duiFrame;
//#define UI_WNDSTYLE_FRAME (WS_VISIBLE | WS_OVERLAPPEDWINDOW)
duiFrame.Create(NULL, _T("测试"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
duiFrame.ShowWindow();
CPaintManagerUI::MessageLoop();
return 0;
}
这些代码就可以帮助我们生成基本的框架窗口,另外我们需要时刻记住的是duilib是对win32 API的封装,所以可以直接使用win32的编程方式,如果以后有不会用的地方完全可以使用win32 的API来完成相关的功能的编写。
框架的剖析
既然它能够生成单文档的框架窗口,那么代码中所做的几步基本上与用纯粹的win32 API相同,所以我们沿着这个思路来进行框架的简单剖析。
主函数中首先是代码CPaintManagerUI::SetInstance(hInstance);至于类CPaintManagerUI到底有什么作用,这个我也不太清楚,现在我还没有仔细看关于这个类的相关代码,这句话主要还是获取了进程的实例句柄。现在先不关心这个。下面的几步主要是在类CDuiFrameWnd中完成或者说在它的基类CWindowWnd中完成。
创建窗口类
主函数中的第二段代码主要完成的是类CDuiFrameWnd对象的创建,我们跟到对应的构造函数中发现它并没有做多余的操作,现在先不管它是如何构造的,它下面就是调用了类的Create函数创建了一个窗口,这个函数的代码如下:
HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
{
if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
ASSERT(m_hWnd!=NULL);
return m_hWnd;
}
我们主要来看第二个if中的代码,首先获得了父窗口的字符串为NULL,然后执行RegisterWindowClass,我们进一步跟到RegisterWindowClass中,它的代码如下:
bool CWindowWnd::RegisterWindowClass()
{
WNDCLASS wc = { 0 };
wc.style = GetClassStyle();
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hIcon = NULL;
wc.lpfnWndProc = CWindowWnd::__WndProc;
wc.hInstance = CPaintManagerUI::GetInstance(); //之前设置的实例句柄在这个地方使用
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = GetWindowClassName();
ATOM ret = ::RegisterClass(&wc);
ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
}
我们发现首先进行的是窗口类的创建,在创建窗口类时主要关心的是窗口类的lpfnWndProc成员和lpszClassName 。lpszClassName 调用了函数GetWindowClassName,这个函数我们在派生类中进行了重写,所以根据多态它会调用派生类的GetWindowClassName函数,将我们给定的字符串作为窗口类的类名
注册窗口类
从上面的代码可以看出注册的代码也是放在RegisterWindowClass中。在最后调用了RegisterClass函数完成了注册。
创建窗口
当RegisterWindowClass执行完成后,会接着执行下面的代码,也就是 m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);完成创建窗口的任务。
显示窗口
Create函数执行完成后,会接着执行下面的duiFrame.ShowWindow();我们跟到这个函数中,函数代码如下:
void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)
{
ASSERT(::IsWindow(m_hWnd));
if( !::IsWindow(m_hWnd) ) return;
::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);
}
函数ShowWindow默认传入参数为bShow = true bTakeFocus = false;在最后进行ShowWindow函数的调用时,根据bShow和bTakeFocus来进行值得传入,根据代码我们发现,当不传入参数时调用的其实是这样的代码ShowWindow(m_hWnd, SW_SHOWNOACTIVATE);
消息循环
消息循环其实是通过代码CPaintManagerUI::MessageLoop();完成,我们跟到MessageLoop函数中看
MSG msg = { 0 };
while( ::GetMessage(&msg, NULL, 0, 0) ) {
if( !CPaintManagerUI::TranslateMessage(&msg) ) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
在这个函数中完成了消息循环。
回调函数
上面我们留了一个lpfnWndProc函数指针没有说,现在来说明这个部分,跟进到对应的构造函数中,发现类本身不做任何操作,但是父类的构造函数进行了相关的初始化操作,下面是对应的代码
CWindowWnd::CWindowWnd() : m_hWnd(NULL), m_OldWndProc(::DefWindowProc), m_bSubclassed(false)
{
}
这样就将lpfnWndProc指向了__WndProc,用于处理默认的消息。
这是一个静态的处理函数,下面是它的代码:
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowWnd* pThis = NULL;
if( uMsg == WM_NCCREATE ) {
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
pThis->m_hWnd = hWnd;
//当开始创建窗口将窗口类对象的指针放入到对应的GWLP_USERDATA字段中
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
}
else {
//取出窗口类对象的指针
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( uMsg == WM_NCDESTROY && pThis != NULL ) {
LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
if( pThis->m_bSubclassed ) pThis->Unsubclass();
pThis->m_hWnd = NULL;
pThis->OnFinalMessage(hWnd);
return lRes;
}
}
if( pThis != NULL ) {
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
上述的代码,在创建窗口时将窗口类对象指针存入到对应的位置便于在其他位置取出并使用。通过return pThis->HandleMessage(uMsg, wParam, lParam);这句话调用的具体对象的HandleMessage,我们在对应的派生类中定义了相应的虚函数,所以根据多态它会调用我们重写的虚函数来处理具体消息,至于我们不关心的消息,它会调用LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);或者DefWindowProc,通过对基类的构造函数的查看,我们发现其实m_OldWndProc就是DefWindowProc。
总结
上面我们说明了duilib的基本框架,下面来总结一下:
1. CPaintManagerUI::SetInstance(hInstance);设置进程的实例句柄,这个值会在注册窗口类时使用
2. 在CWindowWnd类中由Create函数完成窗口类的创建于注册,以及窗口的创建工作
3. CWindowWnd类中的ShowWindow函数用于显示窗口
4. 消息循环由CPaintManagerUI::MessageLoop();代码完成
5. 最后需要重写MessageHandle函数用于处理我们感兴趣的消息。并且在最后需要调用基类的MessageHandle函数,主要是为了调用DefWindowProc处理我们不感兴趣的消息。
duilib基本框架的更多相关文章
- Duilib学习笔记《01》— duilib整体框架认识
从GoogleCode上下载的duilib工程中附带的一副总体设计图(如下所示),可以先整体了解一下,有个初步的认识,对后续进一步深入了解学习会很有帮助. 通过设计图有了一个初步认识后,接下来开始进一 ...
- Duilib总体框架
从GoogleCode上下载的duilib工程中附带的一副总体设计图(如下所示),可以先整体了解一下,有个初步的认识,对后续进一步深入了解学习会很有帮助. 通过设计图有了一个初步认识后,接下来开始进一 ...
- MFC中混合使用Duilib制作界面
因为公司项目最近入了MFC的这个大坑,用MFC做UI做了一段时间,感觉不是很方便,开发效率有点慢. 看了c++里面做界面的类库,感觉Duilib比较符合做界面的需求,而且很多大公司也在使用Duilib ...
- Duilib学习笔记《03》— 控件使用
在前面已经对duilib有个一个基本的了解,并且创建了简单的空白窗体.这仅仅只是一个开始,如何去创建一个绚丽多彩的界面呢?这就需要一些控件元素(按钮.文本框.列表框等等)来完善. 一. Duilib控 ...
- duilib学习领悟(1)
学习duilib已经有一段时间,一直没时间写总结,今天得出空来,写写心得体会! 由于本人知识有限,若有错误地方,望批评指正.多谢.! 初识duilib 刚开始接触duilib的时候,觉的他好神奇,整个 ...
- Duilib入门文档提供下载
版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] Duilib入门文档 基本框架 编写界面xml 响应事件 贴图描述 类html文本描述 动态换肤 Dll插件 资源打包 Duil ...
- 12DUILib经典教程(实例)
Duilib经典实例教程:1基本框架:一个简单的Duilib程序一般是下面这个样子的:://Duilib使用设置部分:#pragmaonce:#defineWIN32_LEAN_AND_ME:#def ...
- Duilib源码分析(一)整体框架
Duilib界面库是一款由杭州月牙儿网络技术有限公司开发的界面开源库,以viksoe项目下的UiLib库的基础上开发(此后也将对UiLib库进行源码分析):通过XML布局界面,将用户界面和处理逻辑彻底 ...
- Duilib源码分析(六)整体流程
在<Duilib源码分析(一)整体框架>.<Duilib源码分析(二)控件构造器—CDialogBuilder>以及<Duilib源码分析(三)XML解析器—CMarku ...
随机推荐
- 2016年7月微软MVP申请開始了!
2016年7月微软MVP申请開始了! CSDN与微软合作,长期为用户提供申请"微软最有价值专家"的平台.希望有兴趣.资历的朋友以及正在朝这个方向努力的朋友能够积极參与. 2016年 ...
- hibernate5(9)注解映射[1]多对一单向关联
在博客站点中,我们可能须要从某一篇文章找到其所关联的作者.这就须要从文章方建立起对用户的关联,即是多对一的映射关系. 如今先看一个配置实例:我们的文章实体类 package com.zeng.mode ...
- hdu 4409 Family Name List(LCA&有坑点)
Family Name List Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- 自学Zabbix3.8.1.1-可视化Visualisation-Graphs简单图表
自学Zabbix3.8.1.1-可视化Visualisation-Graphs简单图表 Zabbix提供了一些简单的图表,用于可视化由项目收集的数据. 用户不需要进行配置工作来查看简单的图表.他们是由 ...
- Pycharm配置(三)
1.准备工作 (1)Python版本为2.7或者更高版本 (2)已经创建了一个Python工程并且添加了内容,具体参考: Getting Started tutorial2.第一步——运行代码 打开之 ...
- Delphi中的RectTracker - 原创
本文算是副产品,正品是利用FFmpeg从任意视频中生成GIF片段的小程序,写完了就发. 因为要对视频画面进行框选,再生成GIF,所以得有个框选的控件,可Delphi里没有啊,只好自己写一个了. 声明 ...
- windows环境下mysql主从配置
mysql主从配置. 相关理论知识可以百度一下,这里就不多说了,直接说如何配置. 一.环境介绍及说明 主库所在的操作系统:win7 主库的版本:mysql-5.6.24-winx64.zip 主库的i ...
- 栈stack(2):栈的链表实现
定义 从上一篇我们知道,栈(stack)是一个只允许一端进行删除插入操作的线性表.同时,我们联想到线性表的链式结构,其特点是用一组任意的存储单元存储线性表的数据元素,因此我们选择使用链表去实现栈,规定 ...
- XMPP协议的基本理解
即时通讯技术简介 即时通讯技术(IM)支持用户在线实时交谈.如果要发送一条信息,用户需要打开一个小窗口,以便让用户及其朋友在其中输入信息并让交谈双方都看到交谈的内容.大多数常用的即时通讯发送程序都会提 ...
- Xamarin截取/删除emoji表情bug解决方案
大家都知道,一个英文=1字节,一个汉字2字节,而一个emoji表情=4个字节,在有这三种混用的时候,比如app聊天界面,那么删除和截取便成了很头痛的事情. 问题描述 截取导致乱码,如下图: 解决方案 ...