知道了如何用基本的windowsAPI创建一个窗口,但现实中我们不可能完全使用这种形式来完成一个复杂的窗口程序。为此,我们必须进行一定的封装。
封装必须要完成几件基本的事情:
(1) 注册和创建窗口的封装;
(2) 窗口过程函数的封装,因为窗口过程函数是一个全局函数,为了实现各种窗口的响应,我们必须实现窗口将每个窗口的消息分发给相应的窗口实例。这也是一个封装最核心的机制,其直接关系到了UI库的运行效率等问题。
(3) 控件等的封装,一套库的应用性也是从这点来区分的。
Moudule
在上一章节中我们发现,无论是注册、创建窗口都需要HINSTANSE这个应用程序句柄,一种方法定义一个全局的变量来记录这个数据,另外一种方法就是我们可以创建一个全局的类来维护这些全局数据,在MFC中我们见到CWinApp,ATL中的则是CAtlModule。以ATL为例,其定义了一个全局的__AtlWinModule(类似这个名字),其各种基类里面所用到的HINSTANCE均是通过其获取。同事Moudle里面还维护了其他的一些信息,MFC中可能还会有HWND的映射表等。我们也来定义一个我们自己的最简单的Moudle吧。
class XWinModule
{
HINSTANCE m_hInst;
public:
XWinModule(HINSTANCE hInst) : m_hInst(hInst)
{
}
void Init(HINSTANCE hInst)
{
m_hInst = hInst;
}
HINSTANCE GetInstance()
{
return m_hInst;
}
};
extern XWinModule _XModule;
在这里我们先不考虑线程安全,就只考虑单线程窗口程序吧。
用的时候如下:
XWinModule _XModule;
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
_XModule.Init(hInstance);
// 创建窗口并显示
// ... ...
// 主消息循环:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
注册和创建窗口
这点比较简单,在ATL中也是定义了一个数据结构CWndClassInfo和宏DECLARE_WND_CLASS来定义并注册WNDCLASS。可以直接去参考ATL代码(atlwin.h)。为了方便以后的扩展,我们也定义我们自己的XWndClassInfo和对应的宏。首先就是封装WndClassEx和RegisterClassEx到一起,然后为了能够方便的获取窗口类和类名,在定义一个宏来创建两个静态函数。如下代码,就不再解释了。
struct XWndClassInfo
{
WNDCLASSEX wndclass;
ATOM Register()
{
return RegisterClassEx(&wndclass);
}
};
//wndclass宏
#define DECLARE_XWNDCLASS(__STR_CLASS_) \
static XWndClassInfo& GetXWndClassInfo()\
{\
static XWndClassInfo wc = \
{ \
{sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartXWndProc, \
0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, __STR_CLASS_, NULL }\
};\
return wc;\
}\
static LPCTSTR GetXWndClassName()\
{ \
return __STR_CLASS_;\
}
窗口基类
封装窗口没有一个基类来定义一堆基本的函数是不行的,一个简单的窗口基类应该维护一个窗口的句柄HWND,同时提供一堆围绕该句柄的操作函数,比如创建、销毁、显示设置style等函数。ATL中的CWindow就是如此,其还会有CWindowImplBase、CWindowImpl等模板类完善其功能。
在本文中,我们也就定义两层吧,一个是XWindow负责基本的函数和hwnd封装,通常一个window也会含有一个默认的rect来维护窗口大小。
class XWindow
{
public:
HWND m_hWnd;
static RECT rcDefault;
public:
XWindow(HWND hwnd = NULL) : m_hWnd(hwnd)
{
}
public:
BOOL ShowWindow(int cmd)
{
ATLASSERT(m_hWnd != NULL);
return ::ShowWindow(m_hWnd, cmd);
}
BOOL UpdateWindow()
{
ATLASSERT(m_hWnd != NULL);
return ::UpdateWindow(m_hWnd);
}
BOOL DestroyWindow()
{
ATLASSERT(::IsWindow(m_hWnd));
if(!::DestroyWindow(m_hWnd))
return FALSE;
m_hWnd = NULL;
return TRUE;
}
};
RECT XWindow::rcDefault = {CW_USEDEFAULT, CW_USEDEFAULT, 0, 0};
另外定义一个XWindowImpl模板类来创建注册窗口,同时定义一个静态的窗口过程函数。
template<class T>
class XWindowImpl: public XWindow
{
public:
XWindowImpl()
{
}
HWND Create(LPCTSTR szTitle, RECT* pRect = nullptr)
{
T::GetXWndClassInfo().Register();
RECT rc;
if (pRect == nullptr)
rc = rcDefault;
else
rc = *pRect;
m_hWnd = ::CreateWindow(T::GetXWndClassName(), szTitle, WS_OVERLAPPEDWINDOW,
rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top,
nullptr, nullptr, _XModule.GetInstance(), nullptr);
ATLASSERT(m_hWnd != nullptr);
return m_hWnd;
}
protected:
static LRESULT CALLBACK StartXWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
};
好了,基类也完成了,这样基本工作算是完成了。当然窗口过程函数最好在加上WM_CLOSE和WM_DESTROY消息的处理,这样能够关闭窗口。为了简化代码我们就先不加,毕竟后面还有更精彩的封装,没必要在这浪费体力。
但有一个点窗口过程函数里面所有的消息需要走下默认的消息处理函数DefWindowProc来让windows帮我们处理,不然窗口会创建不成功的。Windows窗口消息有一点要注意:所有的消息最好都要被处理,要么我们捕获处理,要么就交给windows系统处理。
第一次试航
好了,写了这么多代码,必须要试一下,不然都没成就感了。下面就创建我们第一个窗口类吧。
class SimpleWindow : public XWindowImpl<SimpleWindow>
{
public:
DECLARE_XWNDCLASS(L"SimpleWindow") // 定义窗口类
public:
SimpleWindow()
{
}
};
很简单吧,再到main函数里面显示下
_XModule.Init(hInstance);
// 创建窗口并显示
// ... ...
SimpleWindow simWnd;
simWnd.Create(L"第一个窗口");
simWnd.ShowWindow(SW_SHOW);
simWnd.UpdateWindow();
// 主消息循环:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
编译运行,窗口出来啦!!稍有点兴奋了吧。经过封装后创建个窗口好容易啊,有木有。