最近我一个同学在项目中使用到了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基本框架的更多相关文章

  1. Duilib学习笔记《01》— duilib整体框架认识

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

  2. Duilib总体框架

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

  3. MFC中混合使用Duilib制作界面

    因为公司项目最近入了MFC的这个大坑,用MFC做UI做了一段时间,感觉不是很方便,开发效率有点慢. 看了c++里面做界面的类库,感觉Duilib比较符合做界面的需求,而且很多大公司也在使用Duilib ...

  4. Duilib学习笔记《03》— 控件使用

    在前面已经对duilib有个一个基本的了解,并且创建了简单的空白窗体.这仅仅只是一个开始,如何去创建一个绚丽多彩的界面呢?这就需要一些控件元素(按钮.文本框.列表框等等)来完善. 一. Duilib控 ...

  5. duilib学习领悟(1)

    学习duilib已经有一段时间,一直没时间写总结,今天得出空来,写写心得体会! 由于本人知识有限,若有错误地方,望批评指正.多谢.! 初识duilib 刚开始接触duilib的时候,觉的他好神奇,整个 ...

  6. Duilib入门文档提供下载

    版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] Duilib入门文档 基本框架 编写界面xml 响应事件 贴图描述 类html文本描述 动态换肤 Dll插件 资源打包 Duil ...

  7. 12DUILib经典教程(实例)

    Duilib经典实例教程:1基本框架:一个简单的Duilib程序一般是下面这个样子的:://Duilib使用设置部分:#pragmaonce:#defineWIN32_LEAN_AND_ME:#def ...

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

    Duilib界面库是一款由杭州月牙儿网络技术有限公司开发的界面开源库,以viksoe项目下的UiLib库的基础上开发(此后也将对UiLib库进行源码分析):通过XML布局界面,将用户界面和处理逻辑彻底 ...

  9. Duilib源码分析(六)整体流程

    在<Duilib源码分析(一)整体框架>.<Duilib源码分析(二)控件构造器—CDialogBuilder>以及<Duilib源码分析(三)XML解析器—CMarku ...

随机推荐

  1. FSharp 调用 Oracle.ManagedDataAccess.dll

    FSharp 调用 Oracle.ManagedDataAccess.dll 1.Oracle.ManagedDataAccess.dll 的下载地址.好像如今必需要注冊才干下载. 即使是 64 位系 ...

  2. SSRS Fields cannot be used in page headers or footers

    问题环境:SSRS 2005 报表开发 尝试在Page Header中使用Data Set的字段,遇到以下的错误: The value expression for textbox '' refers ...

  3. android 自己创建一个凝视模板

    android  自己创建一个凝视模板 作为一名程序猿 不仅要有一个写代码的能力,养成一个良好的编写习惯也是非常重要的. 今天给大家具体介绍一下怎样创建凝视模板,给每一个类和方法都自己手动去凝视信息也 ...

  4. windows 环境安装oracle11g db 或者RAC 防火墙必需要透过的进程,port

    1.Firewall Exceptions for Oracle Database For basic database operation and connectivity from remote ...

  5. 浅析node.js

    大家好,今天来给大家讨论node.js这个东西,说起这个东西啊,可能大家已经很熟悉了,因为现在市场上运用的越来越广泛,毕竟它的优点还是有目共睹的! 那么,什么是node.js呢?官方给出了这样的定义: ...

  6. linux OSI七层模型、TCP/IP协议栈及每层结构大揭秘

    学习Linux,就算是像小编我这样的小萌新,也知道OSI模型.什么?!你不知道!!! 好吧,这篇秘籍拿走,不谢~~~ 一.两个协议 (1)OSI 协议模型(7层)国际协议    PDU:协议数据单元对 ...

  7. MyBatis SQL处理大于、小于号

    MyBatis mapper文件是xml文件,需要特殊字符如大于号.小于号后需要转义. 原字符 转义后字符 < < <= <= > > > >=

  8. 《Java NIO (中文版)》【PDF】下载

    <Java NIO (中文版)>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062530 NIO (中文版)>[PDF]& ...

  9. 【ANT】java项目生成文件示例

    <?xml version="1.0" ?> <project default="dist"> <property name=&q ...

  10. SpringMVC总结

    本文是对慕课网上"搞定SSM开发"路径的系列课程的总结,详细的项目文档和课程总结放在github上了.点击查看 MVC简介 Model-View-Control,MVC是一种架构模 ...