自定义窗口类(WNDCLASS)程序
对于对话框模板资源的程序比较简单,因为windows系统帮我们做了很多事情,真的是很多事情,想想我们自己手工用代码动态生成对话框上的一些控件试试看,对话框程序基本工作都交给了windows窗口系统了。我们只需要调用一个函数DialogBox,指定对话框资源ID,窗口过程,OK了。Windows窗口系统自动帮我们从可执行文件的资源节读取数据,帮我们创建窗口上的控件,排列位置。更重要的是,对于模态对话框,我们不需要写消息循环的处理代码。所以前面的例子里,代码都非常简单。对于非模态窗口或者自定义的窗口类窗口,就没这么简单了。
接下来,我们就要完全掌控我们自己的程序,注册我们自己的窗口类窗口。经过前面章节的带领,我们应该比较熟悉winx的脾气了,后面的章节,不需要以灌的方式直接说明怎么做了,大不了,看看winx源代码。不知道谁说的,源代码之下,了无秘密。
回顾
创建一个典型的窗口应用程序,分5步:
a. 写好窗口过程函数
b. 注册窗口类RegisterClass,这个函数用到了WndClass数据结构,该结构主要关注窗口过程
c. 创建窗口CreateWindow,用前面创建的窗口类,加上指定大小位置等信息创建一个窗口。
d. 显示窗口
e. 窗口消息循环
如果不熟悉,请自行学习之。
思考
Winx提供了模态对话框类ModelDialog,提供了非模态对话框类ModelessDialog,那么一定会提供自定义窗口类。反正winx代码也不多,搜索了一番,发现frame.h,从中找到了MainFrame类,应该就是我所需要的。
Winx也一定会秉持能替我们做的就替我们做了的原则,帮我们做大部分事情。窗口过程和对消息的处理,是winx的精华之所在,所以这事不用我们操心,还是遵循消息处理函数命名原则就会被调用起来。
RegisterClass需要用户主动调用,将WNDCLASS的概念暴露,这是winx的设计理念,所以需要我们自己完成。那winx是让我们自己调用::RegisterClass呢还是帮我们调用::RegisterClass呢?我猜测,winx帮我们调用。那窗口类名,窗口style等等数据,如何交给winx处理呢?我猜测,winx向我们要这些数据。看看源代码吧:
class window ...
{
static ATOM winx_call RegisterClass(
IN HINSTANCE hInst = GetThisModule())
{
WNDCLASSEX wcex =
{
sizeof(WNDCLASSEX), // cbSize
WindowClass::GetClassStyle(), // style
WindowClass::WindowProc, // lpfnWndProc
0, // cbClsExtra
0, // cbWndExtra
hInst, // hInstance
NULL, // hIcon
WindowClass::GetDefaultCursor(),// hCursor
WindowClass::GetDefaultBkgnd(), // hbrBackground
NULL, // lpszMenuName
WindowClass::GetWndClassName(), // lpszClassName
NULL // hIconSm
};
return ::RegisterClassEx(&wcex);
}
}
这是window基类的RegisterClass函数。需要我们主动调用的就是这个函数。正如我所猜测的那样,RegisterClass所需要的数据,通过向我们询问来得到。
WindowClass::GetClassStyle(), // style
WindowClass::WindowProc, // lpfnWndProc
WindowClass::GetDefaultCursor(),// hCursor
WindowClass::GetDefaultBkgnd(), // hbrBackground
WindowClass::GetWndClassName(), // lpszClassName
WindowClass,就是我们自己定义的窗口类型了,这是类模板参数。这几个函数,不知道window的继承树里是否都实现了,嗯,这里是考C++语法的时候,overload,override和overwrite这几个东东的差别自行搜索吧。我也不完全明白,大意就是GetClassStyle函数,如果基类也实现了,当然,不是virtual,继承类也实现了,则GetClassStyle()调用的是子类的函数,基类的相对于隐藏起来了。如果子类没有实现,就从子类开始,一级一级往上找,找到了就用它。Ok,RegisterClass了解了。
CreateWindow呢?想必由winx完成了吧。搜索代码,“CreateWindow”,include/winx/win/message.h中,
template
class WindowMessage // : public MessagePrototype
{
....
// --> CreateWindow
HWND winx_call Create(
HWND hWndParent = NULL, LPCTSTR szCaption = NULL,
int x = CW_USEDEFAULT, int y = CW_USEDEFAULT,
int nWidth = CW_USEDEFAULT, int nHeight = CW_USEDEFAULT,
int dwStyle = -1, int dwExStyle = -1,
MENUorID hMenuOrID = 0, HINSTANCE hInst = GetThisModule())
{
enum { DefaultWindowStyle = WindowClass::DefaultWindowStyle };
enum { DefaultWindowExStyle = WindowClass::DefaultWindowExStyle };
if (dwStyle == -1)
dwStyle = DefaultWindowStyle;
if (dwExStyle == -1)
dwExStyle = DefaultWindowExStyle;
return ::CreateWindowEx(
dwExStyle,
WindowClass::GetWndClassName(),
szCaption,
dwStyle,
x, y, nWidth, nHeight,
hWndParent,
(HMENU)hMenuOrID,
hInst,
_WINX_PWND
);
}
}
嗯,函数Create帮我们完成了。同样需要向我们要几个数据:
WindowClass::DefaultWindowStyle()
WindowClass::DefaultWindowExStyle()
WindowClass::GetWndClassName(),
最后消息循环怎么处理呢?由哪部分完成呢?搜索“TranslateMessage”即可。
Include/winx/basic.h RunMsgLoop函数调用了。如下:
// -------------------------------------------------------------------------
// RunMsgLoop
#define WINX_MSGF_MODELDLG 0
#define WINX_MSGF_BASE 0x8100
#define WINX_MSGF_MAINLOOP (WINX_MSGF_BASE + 1)
inline int winx_call RunMsgLoop(int codeMsgf = WINX_MSGF_MAINLOOP)
{
for(;;)
{
MSG msg;
BOOL bRet = ::GetMessage(&msg, NULL, 0, 0);
if (bRet == -1)
{
WINX_TRACE("GetMessage returned -1 (error)/n");
continue; // error, don't process
}
if (!bRet)
{
WINX_TRACE("RunMsgLoop - exiting/n");
return (int)msg.wParam; // WM_QUIT, exit message loop
}
if (!::CallMsgFilter(&msg, codeMsgf))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
嗯,看来不用我们自己手工写消息循环了。就用这东东吧。
基本构件都找到了,可以开始我们自己的代码旅程了。
步骤
创建一个空的win32应用程序。
新建一个cpp文件,内容如下:
#include "winx.h"
template <class WindowClass, class HandleClass = winx::DefaultWindowHandle>
class CWYMainFrame : public winx::MainFrame
{
};
class CMyMainWnd : public CWYMainFrame
{
public:
static TCHAR* GetWndClassName(){ return _T("wygui_window");}
};
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
CMyMainWnd wnd;
wnd.RegisterClass();
wnd.Create(NULL,_T("标题") );
wnd.ShowWindow( SW_SHOW );
RunMsgLoop();
return 0;
}
分析
template <class WindowClass, class HandleClass = winx::DefaultWindowHandle>
class CWYMainFrame : public winx::MainFrame
{
};
这是一个空的子类,什么都不做。为什么要这么演示呢?假设我现在想在winx的基础上,开发一套皮肤库来,显然是希望用户继承自我的窗口了。在我的这个窗口类里,我去完成一些事情。
这里需要注意DefaultWindowHandle前面要加上一个winx命名空间名字。第一次写的时候,顺手复制了MainFrame的定义,结果死活编译不过去,提示DefaultWindowHandle找不到,还以为是头文件没有包含进去。
class CMyMainWnd : public CWYMainFrame
{
public:
static TCHAR* GetWndClassName(){ return _T("wygui_window");}
};
这里就实现了一个函数而已。直接返回一个字符串。这是前面分析的结果。注意static关键字。具体原因,以后说到,如果心急想现在知道,自己看看代码就清楚了。
至于其他几个Get函数没有提供,因为winx帮我们做了。
WinMain函数很简单
CMyMainWnd wnd;
wnd.RegisterClass();
wnd.Create(NULL,_T("标题") );
wnd.ShowWindow( SW_SHOW );
RunMsgLoop();
定义一个主窗口,注册,创建,显示,Loop,嗯,基本是SDK的写法,很符合sdk的习惯。但是又很方便,真实爱死winx了。
运行之,结果是一个白板窗口。
Winx的helper 宏
WINX_CLASS_EX
#define WINX_CLASS_EX(ClassName) /
public: /
static LPCTSTR winx_call GetWndClassName() { /
return ClassName; /
}
该宏往类里插如GetWndClassName,参数为类的字符串。
WINX_CLASS_STYLE
#define WINX_CLASS_STYLE(style) /
public: /
static UINT winx_call GetClassStyle() { return (style); } /
private:
该宏定义wndclass的类型。
窗口类型的宏
#define WINX_DEFAULT_STYLE(style) /
public: /
enum { DefaultWindowStyle = style }
#define WINX_DEFAULT_EXSTYLE(style) /
public: /
enum { DefaultWindowExStyle = style }
一个无标题栏的窗口
我们定义一个无标题栏的窗口玩玩。
class CMyMainWnd : public CWYMainFrame
{
public:
//static TCHAR* GetWndClassName(){ return _T("wygui_window");}
WINX_CLASS_EX( _T("wygui_window") );
public:
UINT winx_msg OnNcHitTest(HWND hWnd, winx::CPoint point)
{
return HTCAPTION;
}
};
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
CMyMainWnd wnd;
wnd.RegisterClass();
wnd.Create(NULL,_T("标题") ,300,400,300,300,WS_POPUP | WS_SYSMENU | WS_SIZEBOX);
wnd.ShowWindow( SW_SHOW );
RunMsgLoop();
return 0;
}
需要说明的是,一般winx的宏会在结束的时候加一个private:导致后面跟着的东西变成了私有的。
WS_POPUP | WS_SYSMENU | WS_SIZEBOX这类型可以随意组合。在Create的时候可以带窗口style,也可以不带参数,填-1,则会使用默认的style。我们可以自己定义一个默认的style,方法是用宏。可以试试看。