文章目录
- 1、duilib的简介
- 2、基本的框架窗口
- 3、框架的剖析
- 3.1、创建窗口类
- 3.2、注册窗口类
- 3.3、创建窗口
- 3.4、显示窗口
- 3.5、消息循环
- 3.6、回调函数
- 4、总结
1、duilib的简介
国内首个开源 的directui 界面库,它提供了一个所见即所得的开发工具——UIDesigner,它只有主框架窗口,其余的空间全部采用绘制的方式实现,所以对于控件来说没有句柄和窗口类等内容,它通过UIDesigner工具将用户定义的窗口保存在xml文件中,在创建窗口时读取xml文件中的内容,来绘制相应的控件。目前有许多界面采用duilib编写,大家可以去网上搜集相关资料。
2、基本的框架窗口
首先新建一个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来完成相关的功能的编写。
3、框架的剖析
既然它能够生成单文档的框架窗口,那么代码中所做的几步基本上与用纯粹的win32 API相同,所以我们沿着这个思路来进行框架的简单剖析。
主函数中首先是代码CPaintManagerUI::SetInstance(hInstance);至于类CPaintManagerUI到底有什么作用,这个我也不太清楚,现在我还没有仔细看关于这个类的相关代码,这句话主要还是获取了进程的实例句柄。现在先不关心这个。下面的几步主要是在类CDuiFrameWnd中完成或者说在它的基类CWindowWnd中完成。
3.1、创建窗口类
主函数中的第二段代码主要完成的是类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函数,将我们给定的字符串作为窗口类的类名。
3.2、注册窗口类
从上面的代码可以看出注册的代码也是放在RegisterWindowClass中。在最后调用了RegisterClass函数完成了注册。
3.3、创建窗口
当RegisterWindowClass执行完成后,会接着执行下面的代码,也就是 m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this); 完成创建窗口的任务。
3.4、显示窗口
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);
3.5、消息循环
消息循环其实是通过代码CPaintManagerUI::MessageLoop();完成,我们跟到MessageLoop函数中看
MSG msg = { 0 };while( ::GetMessage(&msg, NULL, 0, 0) ) {if( !CPaintManagerUI::TranslateMessage(&msg) ) {::TranslateMessage(&msg);::DispatchMessage(&msg);}}
在这个函数中完成了消息循环。
3.6、回调函数
上面我们留了一个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。
CWindowWnd::CWindowWnd() : m_hWnd(NULL), m_OldWndProc(::DefWindowProc), m_bSubclassed(false)
{
}
4、总结
上面我们说明了duilib的基本框架,下面来总结一下:
- CPaintManagerUI::SetInstance(hInstance);设置进程的实例句柄,这个值会在注册窗口类时使用 。就是先设置.exe文件的路径,然后为了后续设置xml的路径。
- 在CWindowWnd类中由Create函数完成窗口类的创建于注册,以及窗口的创建工作 。
- CWindowWnd类中的ShowWindow函数用于显示窗口 。
- 消息循环由CPaintManagerUI::MessageLoop();代码完成 。
- 最后需要重写HandleMessage()函数用于处理我们感兴趣的消息。并且在最后需要调用基类的HandleMessage()函数,主要是为了调用DefWindowProc处理我们不感兴趣的消息。