首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
105 阅读
2
nvim番外之将配置的插件管理器更新为lazy
78 阅读
3
2018总结与2019规划
62 阅读
4
PDF标准详解(五)——图形状态
40 阅读
5
为 MariaDB 配置远程访问权限
33 阅读
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
linux
文本编辑器
Java
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
314
篇文章
累计收到
31
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
页面
归档
友情链接
关于
搜索到
2
篇与
的结果
2017-01-16
duilib基本流程
duilib的基本流程如上图,通过解析一个xml文件,将文件中的内容渲染为窗口界面,这个解析过程由WindowImplBase类来完成。基本框架如下:首先在公共头文件中加入如下内容:#include <objbase.h> #include <DuiLib\UIlib.h> using namespace DuiLib; #ifdef _DEBUG #pragma comment(lib, "DuiLib_ud.lib"); #else #pragma comment(lib, "DuiLib_d.lib"); #endif这个是duilib的一些基本配置从WindowImplBase类中派生一个类,然后实现这样3个基本函数:virtual CDuiString GetSkinFolder() { return _T("skin"); }; virtual CDuiString GetSkinFile() { return _T("HelloWnd.xml"); }; virtual LPCTSTR GetWindowClassName(void) const { return _T("HelloWnd"); };这三个函数的说明如下:1、 GetSkinFolder () 需要返回 皮肤XML 所在的文件夹2、GetSkinFile () 需要返回 皮肤 XML 的文件名(也可以包含路径)3、GetWindowClassName () 需要返回这个窗口的类名,这个类名用于 RegisterClass.这三个函数告知duilib库应该从哪个文件夹下解析哪个xml文件,并定义对应窗口的名字,以后这个类就代表这个xml文件所描述的窗口需要注意的是这些函数必须在头文件中这样写,我自己写在CPP文件中它在运行时报错,可能是库本身的bug在WinMain主函数中添加如下代码int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) { CPaintManagerUI::SetInstance(hInstance);// 加载XML的时候,需要使用该句柄去定位EXE的路径,才能加载XML的路径 CHelloWnd* wnd = new CHelloWnd; // 生成对象 wnd->Create(NULL, NULL, UI_WNDSTYLE_DIALOG, 0); // 创建DLG窗口 wnd->CenterWindow(); // 窗口居中 // wnd->ShowWindow();//作为非模态对话框显示 wnd->ShowModal(); // 显示 // CPaintManagerUI::MessageLoop(); // 消息循环,是一个默认的消息循环,什么消息都不响应 delete wnd; // 删除对象 return 0; }在这创建了一个对话框,但是如果加上消息循环就表示它是一个非模态对话框,这个窗口我没有给它菜单栏,也就没有关闭按钮,如果作为非模态对话框,要加上一句CPaintManagerUI::MessageLoop();给它一个消息循环。但是它将不能关闭,只能通过任务管理器强制结束,使用ShowModal表示将它作为模态对话框,在win32中模态对话框使用它自己的消息循环,也就不需要自己给它一个消息循环,它可以在任务栏上被关闭。类的Create函数定义如下:HWND Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x = CW_USEDEFAULT, int y = CW_USEDEFAULT, int cx = CW_USEDEFAULT, int cy = CW_USEDEFAULT, HMENU hMenu = NULL);可以看到它就是对WIN32中CreateWindow的封装,在这duilib为窗口自定义了一些类型,其中主要的类型如下:#define UI_WNDSTYLE_CONTAINER (0) #define UI_WNDSTYLE_FRAME (WS_VISIBLE | WS_OVERLAPPEDWINDOW) #define UI_WNDSTYLE_CHILD (WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN) #define UI_WNDSTYLE_DIALOG (WS_VISIBLE | WS_POPUPWINDOW | WS_CAPTION | WS_DLGFRAME | WS_CLIPSIBLINGS | WS_CLIPCHILDREN) //下面是窗口的扩展类型 #define UI_WNDSTYLE_EX_FRAME (WS_EX_WINDOWEDGE) #define UI_WNDSTYLE_EX_DIALOG (WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME) //下面是窗口类类型 #define UI_CLASSSTYLE_CONTAINER (0) #define UI_CLASSSTYLE_FRAME (CS_VREDRAW | CS_HREDRAW) #define UI_CLASSSTYLE_CHILD (CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_SAVEBITS) #define UI_CLASSSTYLE_DIALOG (CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_SAVEBITS)大部分都是对win32中窗口类型的一个组合。如果熟悉WIN32编程,那么很容易知道这些都代表什么
2017年01月16日
6 阅读
0 评论
0 点赞
2016-07-27
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的基本框架,下面来总结一下:CPaintManagerUI::SetInstance(hInstance);设置进程的实例句柄,这个值会在注册窗口类时使用在CWindowWnd类中由Create函数完成窗口类的创建于注册,以及窗口的创建工作CWindowWnd类中的ShowWindow函数用于显示窗口消息循环由CPaintManagerUI::MessageLoop();代码完成最后需要重写MessageHandle函数用于处理我们感兴趣的消息。并且在最后需要调用基类的MessageHandle函数,主要是为了调用DefWindowProc处理我们不感兴趣的消息。
2016年07月27日
7 阅读
0 评论
0 点赞