在C++类中实现Windows窗口的创建
//========================================================================
//TITLE:
// 在C++类中实现Windows窗口的创建
//AUTHOR:
// norains
//DATE:
// Thursday 9-November-2006
//========================================================================
在面向过程的方法中实现窗口的创建很简单,但有个非常明显的缺点,就是封装不好。如果是自写自用,倒不是一个很大的问题,但如果是写给用户的,可能用户在包含头文件之后,看到那一大堆函数以及变量声明就已经晕掉。最好的方法当然是使用类,只给使用者留出应该使用的接口。但这会有个问题,就是消息处理函数必须为static的CALLBACK,否则编译会出错;但如果消息处理函数为static,其就归属于类,就根本无法调用对象的成员函数,而这个在经典的《windows 程序设计》中也没有涉及到。
难道我们就只能束手无策,只能采用面向过程的方法了么?那倒未必,毕竟在MFC框架中已经做到,那么相信我们也可以做到。经过一番摸索,终于找到在类中创建窗口的方法,在此不敢独享,放出代码和大家探讨。
为便于理解,代码的作用很简单,仅仅是完成了类的声明和对话框的创建,唯一多余的是在接收到WM_INITDIALOG消息时弹出一个消息框,以证明private函数能够正常调用。
代码首先是声明一个CMainDlg类:
///////////////////////////
//MainDlg.h
///////////////////////////
class CMainDlg
{
//--------------------------------------------------------------------------------------------
public:
//静态的回调函数
static BOOL CALLBACK CMainDlg::MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam);
//-------------------------------------------------------------------------------------------
//Function
public:
//构造函数
CMainDlg(HINSTANCE hInst);
//析构函数
virtual ~CMainDlg();
//创建对话框
BOOL CreateMainDlg();
//消息调用函数,具体意义请参见函数的实现
BOOL CallDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam);
private:
//临时测试调用函数,仅作为调试用
void TempTestFuncion(){MessageBox(NULL,L"test",L"",NULL);};
//---------------------------------------------------------------------------------------------
//数据成员
private:
HINSTANCE m_hInst;
HWND m_hDlg;
};
然后是MainDlg类的实现:
/////////////////////////////////
//MainDlg.cpp
////////////////////////////////
#include "stdafx.h"
#include "MainDlg.h"
#include "resource.h"
//全局指针,用来指向所创建的对象,即this的指向对象
CMainDlg *g_pDlg = NULL;
//消息的回调函数的实现
BOOL CALLBACK CMainDlg::MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
{
if(g_pDlg != NULL)
{
//如果this指针不为空,则调用CallDlgProc函数
return g_pDlg->CallDlgProc(hWnd,wMsg,wParam,lParam);
}
return FALSE;
}
//构造函数
CMainDlg::CMainDlg(HINSTANCE hInst)
{
g_pDlg = this; //存储this对象到全局变量中
m_hInst = hInst;
}
//析构函数
CMainDlg::~CMainDlg()
{
}
//创建对话框
BOOL CMainDlg::CreateMainDlg()
{
m_hDlg = CreateDialog(m_hInst,MAKEINTRESOURCE(IDD_MAIN_DLG),NULL,MainDlgProc);
if(m_hDlg == NULL)
{
return FALSE;
}
ShowWindow(m_hDlg,TRUE);//显示窗口
return TRUE;
}
//真正的消息处理函数,在这里可以任意调用对象的成员函数
BOOL CMainDlg::CallDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
{
switch(wMsg)
{
case WM_INITDIALOG:
TempTestFuncion(); //norains:测试的私有函数
break;
}
return FALSE;
}
这是在主程序中对类的的调用:
//////////////////
//MainApp.cpp
//////////////////
#include "stdafx.h"
#include "MainDlg.h"
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
//声明一个对象
CMainDlg mainDlg(hInstance);
//创建并显示窗口
if(mainDlg.CreateMainDlg() == FALSE)
{
return 0x05;
}
//消息循环
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
这段代码的关键点在于MainDlgProc声明为类的static函数,以及使用g_pDlg来存储this对象指针,并在MainDlgProc中调用。只要注意到这两点,在类中创建一个窗口就不是一件非常困难的事情。
对于CallDlgProc()函数,可能有不少人觉得是鸡肋,因为相同的功能完全可以在MainDlgProc()中实现,比如这程序的代码完全可以撇开CallDlgProc()写成这样:
BOOL CALLBACK CMainDlg::MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
{
if(g_pDlg != NULL)
{
switch(wMsg)
{
case WM_INITDIALOG:
g_pDlg->TempTestFuncion();
break;
}
}
return FALSE;
}
所完成的功能和之前的代码相同。不过如果要更改之后的代码正常工作的话,还需要把TempTestFuncion()函数声明为public。也许有的读者已经发现问题之所在:如果对消息相应的处理函数不多,这样倒是可以;但如果很多的话,那么那些消息相应函数都需要声明为public,并且这些消息响应函数都是用户不可能用到的!用户看到如此之多用不到的public函数,即使没头晕,也需要好一阵子才能回神过来。
所以呢,CallDlgProc()函数只是起到一个缓冲作用,不必要把所有的消息相应函数都暴露给用户。虽然这样做也暴露了CallDlgProc()函数,但毕竟相对于上十个甚至上百个消息响应函数而言,仅仅只是暴露一个没什么作用的CallDlgProc()函数函数,应该能让用户减轻不少负担才是。
在网上看到有一个方法不是采用全局变量,而是直接设置窗口数据,然后再获取。这方法比较有意思,在此将更改的代码贴出:
BOOL CMainDlg::CreateMainDlg()
{
m_hDlg = CreateDialog(m_hInst,MAKEINTRESOURCE(IDD_MAIN_DLG),NULL,MainDlgProc);
if(m_hDlg == NULL)
{
return FALSE;
}
ShowWindow(m_hDlg,TRUE);//显示窗口
SetWindowLong(m_hDlg,GWL_USERDATA,(LONG)this); //将this指针传递给窗口
return TRUE;
}
BOOL CALLBACK CMainDlg::MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
{
static CMainDlg * pDlg = NULL;
if(pDlg == NULL)
{
pDlg = (CMainDlg *)GetWindowLong(hWnd,GWL_USERDATA);
}
if(pDlg != NULL)
{
switch(wMsg)
{
case WM_INITDIALOG:
pDlg->TempTestFuncion();
break;
}
}
return FALSE;
}
这段代码关键就只在于两个函数:SetWindowLong()和GetWindowLong().很巧妙地让窗口通过这两个函数来传输this对象,的确是一个很好的方法.