“窗口类、窗口类的对象和窗口”三者的关系
感谢孙鑫老师以及他的《VC深入详解》对本人脱贫顿悟事业的支持!! ~_~..
///********。。。。。。。
很多开发人员都将窗口类、窗口类的对象和窗口之间的关系弄混淆了。为了使读者能更好地理解它们之间的关系,下面我们将模拟CWnd类的封装过程。首先新建一个Win32 Application类型的工程,取名为“WinMain”。在随后的向导窗口中选择创建一个空工程(即选择an empty project选项)。接着为该工程新建一个源文件WinMain.cpp。在该文件中,首先新建一个类CWnd,然后为其定义创建窗口函数(CreateEx)、显示窗口函数(ShowWindow)和更新窗口函数(UpdateWindow)三个函数,并定义一个成员变量(m_hWnd)。具体代码如下:
class CWnd
{
public:
BOOL CreateEx(DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // registered class name
LPCTSTR lpWindowName, // window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // menu handle or child identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam); // window-creation data
BOOL ShowWindow(int nCmdShow);
BOOL UpdateWindow();
public:
HWND m_hWnd;
};
接下来完成这三个函数的定义,代码如xia所示:
BOOL CWnd::CreateEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // registered class name
LPCTSTR lpWindowName, // window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // menu handle or child identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam) // window-creation data
{
m_hWnd=::CreateWindowEx(dwExStyle,lpClassName,dwStyle,x,y,
nWidth,nHeight,hWndParent,hMenu,hInstance,
lpParam);
if(m_hWnd!=NULL)
return TRUE;
else
return FALSE;
}
BOOL CWnd::ShowWindow(int nCmdShow)
{
return ::ShowWindow(m_hWnd,nCmdShow);
}
BOOL CWnd::UpdateWindow()
{
return ::UpdateWindow(m_hWnd);
}
其中,我们定义的CWnd类的CreateEx函数需要完成创建窗口的 工作 ,这可以利用Win32提供的SDK函数:CreateWindowEx函数来实现。该函数返回一个句柄,标识它所创建的窗口。这里,我们就可以利用已定义的CWnd类的成员变量m_hWnd来保存这个窗口句柄。因为我们定义的CreateEx函数返回值是个BOOL型,所以应该判断一下这个窗口句柄。根据其值是否为空来决定函数是返回TRUE值,还是FALSE值。
读者应注意的是,在实际开发时,应该初始化m_hWnd变量,这可以在构造函数中实现,给它赋一个初值NULL。这里我们只是为了演示CWnd类是如何与窗口关联起来的,因此就不进行初始化工作了。
接下来定义ShowWindow函数的实现。同样,需要调用Platform SDK函数,即ShowWindow来完成窗口的显示。为了区分这两个同名函数,在调用这个Platform SDK函数时,前面加上作用域标识符(即::)。这种以“::”开始的表示方法表明该函数是一个全局函数,这里表示调用的ShowWindow函数是Platform SDK函数。因为CreateEx函数已经获取了窗口句柄并保存到m_hWnd成员变量中,所以,ShowWindow函数可以直接把这个句柄变量作为参数来使用。
提示:读者在定义自己的成员函数时,如果调用的API函数名与自己的函数名不同,那么该API函数名前可以加也可以不加“::”符号,编译器会自动识别API函数。但是如果当前定义的成员函数与内部调用的API函数名相同,那么后者前面必须加“::”符号,否则 程序 在编译或运行时就会出错。
我们自己定义的UpdateWindow函数的实现比较简单,直接调用SDK函数:UpdateWindow完成更新窗口的工作。
从以上代码可知,我们定义的CWnd类的后两个函数(ShowWindow和UpdateWindow)内部都需要一个窗口句柄,即需要知道对哪个窗口进行操作。
现在我们就实现了一个窗口类:CWnd。但我们知道如果要以类的方式来完成窗口的创建、显示和更新操作,那么首先还需要编写一个WinMain函数。
Winmain()代码如下:
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
{
//首先是设计窗口类,即定义一个WNDCLASS,并为相应字段赋值。
WNDCLASS wndcls;
wndcls.cbClsExtra=0;
wndcls.cbWndExtra=0;
......
//注册窗口类
RegisterClass(&wndcls);
//创建窗口
CWnd wnd;
wnd.CreateEx(...);
//显示窗口
wnd.ShowWindow(SW_SHOWNORMAL);
//更新窗口
wnd.UpdateWindow();
//接下来就是消息循环,此处省略
......
return 0;
}
许多程序员在进行MFC开发时,容易混淆一点:认为这里的CWnd类型的wnd这个C++对象所代表的就是一个窗口。因为在实践中,他们看到的现象是:当C++窗口类对象销毁时,相应的窗口也就没了。有时正好巧合,当窗口销毁时,C++窗口类对象的生命周期也到了,从而也销毁了。正因为如此,许多 程序员 感觉C++窗口类对象就是窗口,窗口就是这个C++窗口类对象。事实并非如此--读者可以想像一下,如果我们关闭了一个窗口,这个窗口就销毁了,那么该窗口对应的C++窗口类对象销毁了没有呢?当然没有。当一个窗口销毁时,它会调用CWnd类的DestroyWindow函数,该函数销毁窗口后,将CWnd成员变量:m_hWnd设为NULL。
/*************************************此处亮点*****************************************/
C++窗口类对象的生命周期和窗口的生命周期不是一致的。
当一个窗口销毁时,与C++窗口类对象没有关系,它们之间的纽带仅仅在于这个C++窗口类内部的成员变量:m_hWnd,该变量保存了与这个C++窗口类对象相关的那个窗口的句柄。
另一方面,当我们设计的这个C++窗口类对象销毁的时候,与之相关的窗口是应该销毁的,因为它们之间的纽带(m_hWnd)已经断了。另外,窗口也是一种资源,它也占据内存。这样,在C++窗口类对象析构时,也需要回收相关的窗口资源,即销毁这个窗口。
.................................................................................................................................
因此,一定要注意:C++窗口类对象与窗口并不是一回事,它们之间惟一的关系是C++窗口类对象内部定义了一个窗口句柄变量,保存了与这个C++窗口类对象相关的那个窗口的句柄。窗口销毁时,与之对应的C++窗口类对象销毁与否,要看其生命周期是否结束。但C++窗口类对象销毁时,与之相关的窗口也将销毁。在我们定义的这个WinMain 程序 (例3-20所示代码)中,当程序运行到WinMain函数的右大括号(})时,该函数内部定义的Wnd窗口类对象的生命周期也就结束了。
据上总结如下:
窗口
即我们在电脑桌面上看到的应用程序界面,系统为每一个窗口维护了一个WNDCLASS结构体用来描述这个窗口的各种属性(如窗口风格、图标、画刷、菜单和窗口过程等)。系统还会为这个窗口保存它的位置、大小状态以及窗口所属的进程。如果你想查询和操作这些属性,你无法直接操作他们,因为你不知道他们的地址。它们是系统负责维护的。但是系统提供和很多函数让你去操作它们。系统为每个窗口都维护了这些数据结构,你必须指明你要操作哪个窗口。所以窗口句柄呼之欲出了,窗口句柄就是用来标识一个窗口的(以及系统为这个窗口维护的数据结构)。所以Windwos SDK中几乎每一个操作窗口的函数都需要窗口句柄作参数。窗口即是电脑桌面上的界面和系统为这个界面在背后维护的一系列结构体。
窗口类
Windows SDK中创建一个窗口的过程
说到窗口类,还得回顾一下我们在Windows SDK编程时创建一个窗口的过程。
设计窗口类(设置各种属性。注意这个窗口类只是一个结构体,不是我们要讨论的窗口类)
注册窗口类
创建窗口(更加设计的窗口类结构体创建窗口)
显示窗口
更新窗口
消息循环
窗口过程(在窗口过程中你可以根据收到的消息调用不同的函数,而且每个消息都有默认的处理过程DefWindowProc (hwnd, message, wParam, lParam),DefWindowProc会根据消息调用不同的处理函数。)
上面过程中,在窗口创建好后我们可以调用各种函数来操作窗口。多半是在窗口过程中操作串口。很多消息都会有默认的函数调用。
MFC用窗口类帮我们把窗口和上面这些繁琐的过程用一个C++类封装起来了,还根据窗口的不同提供了几种默认风格的窗口类。窗口句柄成了窗口类的公开成员变量m_hWnd,对窗口的操作函数成了窗口类的成员方法。还用宏定义把消息和默认的成员方法关联起来。
窗口类中的方法(函数)分类:
对窗口进行操作的函数:这些函数就是SDK提供的函数的再次封装和升级,只是你再不需要传递窗口句柄给它们们。因为它们可以直接访问类的成员变量m_hWnd。有些是自动调用的,当然都可以显示调用它们。有些还可以重载。
消息响应函数:这些函数都有消息映射(貌似VC是用宏实现的)。不同的消息会有不同的消息响应函数。这些响应函数会调用上面的对窗口进行操作的函数。为了让你改变这些消息的默认处理方式。这些函数都设置成了虚函数,你可以重载它们。
。。。。综上:窗口类:就是一个C++类,它把窗口和对窗口的操作函数封装到了一起。
窗口类对象:就是上面窗口类生成的实例对象。