窗口过程只是一个函数,每个消息都被调用,所以它本质上是无状态的。因此,您需要一种方法来跟踪您的应用程序的状态,从一个函数调用到下一个函数调用。
最简单的方法就是把所有东西放在全局变量中。这对于小程序来说已经足够了,许多SDK样本都使用这种方法。然而,在一个大型项目中,这导致了全局变量的扩散。另外,你可能有几个窗口,每个窗口都有自己的窗口过程。跟踪哪个窗口应该访问哪些变量变得混乱和容易出错。
CreateWindowEx函数提供了一种将任何数据结构传递给窗口的方法。当这个函数被调用时,它将下面的两个消息发送到你的窗口过程:
这些消息按照列出的顺序发送。(这些不是在CreateWindowEx期间发送的唯一两条消息,但是我们可以忽略其他讨论。)
窗口变得可见之前,WM_NCCREATE和WM_CREATE消息被发送。这使得它们成为初始化UI的好地方——例如,确定窗口的初始布局。
CreateWindowEx的最后一个参数是void*类型的指针。你可以在这个参数中传递你想要的任何指针值。当窗口过程处理WM_NCCREATE或WM_CREATE消息时,它可以从消息数据中提取此值。
让我们看看你将如何使用这个参数来传递应用程序数据到你的窗口。首先定义一个保存状态信息的类或结构。
// Define a structure to hold some state information.
struct StateInfo{
// ... (struct members not shown)
};
当你调用CreateWindowEx时,在最终的void*参数中传递一个指向这个结构体的指针。
StateInfo *pState=new (std::nothrow)StateInfo;
if(pState==NULL)
return 0;
// Initialize the structure members (not shown).
HWND hwnd=CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
pState // Additional application data
);
当您收到WM_NCCREATE和WM_CREATE消息时,每个消息的lParam参数是一个指向CREATESTRUCT结构的指针。CREATESTRUCT结构又包含您传递给CreateWindowEx的指针。
显示CREATESTRUCT结构布局的图表
这里是如何提取指向你的数据结构的指针。首先,通过转换lParam参数来获取CREATESTRUCT结构。
CREATESTRUCT *pCreate=reinterpret_cast<CREATESTRUCT*>(lParam);
CREATESTRUCT结构的lpCreateParams成员是您在CreateWindowEx中指定的原始无效指针。通过转换lpCreateParams获取指向自己的数据结构的指针。
pState=reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
接下来,调用SetWindowLongPtr函数,并将指针传递给您的数据结构。
SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)pState);
这最后一个函数调用的目的是将StateInfo指针存储在窗口的实例数据中。一旦你这样做,你总是可以通过调用GetWindowLongPtr从窗口中获取指针:
LONG_PTR ptr=GetWindowLongPtr(hwnd,GWLP_USERDATA);
StateInfo *pState=reinterpret_cast<StateInfo*>(ptr);
每个窗口都有自己的实例数据,因此您可以创建多个窗口,并为每个窗口提供自己的数据结构实例。如果您定义一个窗口类并创建该类的多个窗口(例如,如果创建了自定义控件类),则此方法特别有用。将GetWindowLongPtr调用包装在一个小的帮助函数中很方便。
inline StateInfo* GetAppState(HWND hwnd){
LONG_PTR ptr=GetWindowLongPtr(hwnd,GWLP_USERDATA);
StateInfo *pState=reinterpret_cast<StateInfo*>(ptr);
return pState;
}
现在你可以编写你的窗口过程如下。
LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){
StateInfo *pState;
if(uMsg==WM_CREATE){
CREATESTRUCT *pCreate=reinterpret_cast<CREATESTRUCT*>(lParam);
pState=reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)pState);
}
else
pState=GetAppState(hwnd);
switch(uMsg){
// Remainder of the window procedure not shown ...
}
return TRUE;
}
面向对象的方法
我们可以进一步扩展这个方法。我们已经定义了一个数据结构来保存窗口的状态信息。为数据结构提供对数据进行操作的成员函数(方法)是有意义的。这自然会导致一个设计,其中结构(或类)负责窗口上的所有操作。窗口过程将成为类的一部分。
换句话说,我们想把这个:
// pseudocode
LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){
StateInfo *pState;
/* Get pState from the HWND. */
switch(uMsg){
case WM_SIZE:
HandleResize(pState,...);
break;
case WM_PAINT:
HandlePaint(pState,...);
break;
// And so forth.
}
}
变成这样:
// pseudocode
LRESULT MyWindow::WindowProc(UINT uMsg,WPARAM wParam,LPARAM lParam){
switch(uMsg){
case WM_SIZE:
this->HandleResize(...);
break;
case WM_PAINT:
this->HandlePaint(...);
break;
}
}
唯一的问题是如何挂钩MyWindow::WindowProc方法。RegisterClass函数期望窗口过程是一个函数指针。在这种情况下,您不能将指针传递给(非静态)成员函数。但是,您可以将指针传递给静态成员函数,然后委托给成员函数。这是一个显示这种方法的类模板:
template<class DERIVED_TYPE>
class BaseWindow{
public:
static LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){
DERIVED_TYPE *pThis=NULL;
if(uMsg==WM_NCCREATE){
CREATESTRUCT* pCreate=(CREATESTRUCT*)lParam;
pThis=(DERIVED_TYPE*)pCreate->lpCreateParams;
SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)pThis);
pThis->m_hwnd=hwnd;
}
else
pThis=(DERIVED_TYPE*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
if(pThis)
return pThis->HandleMessage(uMsg,wParam,lParam);
else
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
BaseWindow():m_hwnd(NULL){}
BOOL Create(
PCWSTR lpWindowName,
DWORD dwStyle,
DWORD dwExStyle=0,
int x=CW_USEDEFAULT,
int y=CW_USEDEFAULT,
int nWidth=CW_USEDEFAULT,
int nHeight=CW_USEDEFAULT,
HWND hWndParent=0,
HMENU hMenu=0
){
WNDCLASS wc={0};
wc.lpfnWndProc=DERIVED_TYPE::WindowProc;
wc.hInstance=GetModuleHandle(NULL);
wc.lpszClassName=ClassName();
RegisterClass(&wc);
m_hwnd=CreateWindowEx(
dwExStyle,ClassName(),lpWindowName,dwStyle,x,y,
nWidth,nHeight,hWndParent,hMenu,GetModuleHandle(NULL),this
);
return (m_hwnd?TRUE:FALSE);
}
HWND Window()const{
return m_hwnd;
}
protected:
virtual PCWSTR ClassName()const=0;
virtual LRESULT HandleMessage(UINT uMsg,WPARAM wParam,LPARAM lParam)=0;
HWND m_hwnd;
};
BaseWindow类是一个抽象基类,从中派生出特定的窗口类。例如,下面是从BaseWindow派生的一个简单类的声明:
class MainWindow:public BaseWindow<MainWindow>{
public:
PCWSTR ClassName()const{
return L"Sample Window Class";
}
LRESULT HandleMessage(UINT uMsg,WPARAM wParam,LPARAM lParam);
};
要创建窗口,请调用BaseWindow::Create:
int WINAPI wWinMain(HINSTANCE hInstance,HINSTANCE,PWSTR pCmdLine,int nCmdShow){
MainWindow win;
if(!win.Create(L"Learn to Program Windows",WS_OVERLAPPEDWINDOW))
return 0;
ShowWindow(win.Window(),nCmdShow);
// Run the message loop.
MSG msg={};
while(GetMessage(&msg,NULL,0,0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
纯虚函数BaseWindow::HandleMessage用于实现窗口过程。例如,以下实现等同于模块1开始处显示的窗口过程。
LRESULT MainWindow::HandleMessage(UINT uMsg,WPARAM wParam,LPARAM lParam){
switch(uMsg){
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc=BeginPaint(m_hwnd,&ps);
FillRect(hdc,&ps.rcPaint,(HBRUSH)(COLOR_WINDOW+1));
EndPaint(m_hwnd,&ps);
}
return 0;
default:
return DefWindowProc(m_hwnd,uMsg,wParam,lParam);
}
return TRUE;
}
请注意窗口句柄存储在一个成员变量(m_hwnd)中,所以我们不需要将它作为参数传递给HandleMessage。
许多现有的Windows编程框架(如Microsoft基础类(MFC)和活动模板库(ATL))使用的方法与此处显示的类似。当然,像MFC这样的完全泛化的框架比这个相对简单的例子要复杂得多。
下一个
第2单元:在Windows程序中使用COM
相关话题
BaseWindow示例
原文链接:Managing Application State
返回目录