对于窗口,使用CreateWindowEx
WINUSERAPI
HWND WINAPI CreateWindowExW(
DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle,
int X, int Y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam);
最后一个参数LPVOID lpParam传入this指针,在窗口过程WndProc的WM_CREATE消息中使用lParam获取类型为LPCREATESTRUCT的结构体指针lpcs,然后使用lpcs->lpCreateParams获取传入的this指针,并使用SetWindowLongPtr保存到GWLP_USERDATA窗口字段。
LPCREATESTRUCT lpcs = nullptr;
MyWindow* pThis = nullptr;
switch (message)
{
case WM_CREATE:
lpcs = (LPCREATESTRUCT)lParam;
pThis = (MyWindow*)lpcs->lpCreateParams;
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
// ...
对于对话框,使用DialogBoxParam(模态对话框)或CreateDialogParam(非模态对话框)
WINUSERAPI
INT_PTR WINAPI DialogBoxParamW(
HINSTANCE hInstance, LPCWSTR lpTemplateName, HWND hWndParent,
DLGPROC lpDialogFunc, LPARAM dwInitParam);
WINUSERAPI
HWND WINAPI CreateDialogParamW(
HINSTANCE hInstance, LPCWSTR lpTemplateName, HWND hWndParent,
DLGPROC lpDialogFunc, LPARAM dwInitParam);
最后一个参数LPARAM dwInitParam传入this指针,在对话框过程DlgProc的WM_INITDIALOG消息中使用lParam获取窗口字段,并使用SetWindowLongPtr保存到GWLP_USERDATA窗口字段。
MyWindow* pThis = nullptr;
switch (message)
{
case WM_INITDIALOG:
pThis = (MyWindow*)lParam;
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
// ...
在其它窗口消息中,使用GetWindowLongPtr获取GWLP_USERDATA中保存的this指针。
MyWindow* pThis = nullptr;
switch (message)
{
// ...
case WM_CHAR:
pThis = (MyWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
// ...
【对GWLP_USERDATA窗口字段的分析】
Windows提供了SetWindowLongPtr、GetWindowLongPtr修改窗口字段,其中GWLP_USERDATA可以放置this指针等数据。不过也有人不推荐放置在这里,担心对窗口超类化或子类化有可能覆盖这个字段。实际这种担心并没有必要。
1、使用GWLP_USERDATA字段存储this指针是一种常见用法,很多软件包括Windows操作系统都在使用。
2、子类化的程序有义务在适当时机保存及恢复GWLP_USERDATA,否则程序肯定跑飞,这一般意味着他们的程序出了bug,而不是我们程序出了bug。
3、超类化的程序可以使用GetClassInfoEx获取WNDCLASSEX,然后调整cbWndExtra扩充一个指针大小的字段,并使用所扩充字段的字节偏移调用SetWindowLongPtr和GetWindowLongPtr,而不是使用GWLP_USERDATA。
【对WM_CREATE之前的几个消息的分析】
根据网上众多资料以及写程序试验,WM_CREATE之前WndProc会收到下面的消息:
1、WM_GETMINMAXINFO——查询窗口大小约束
2、WM_NCCREATE——非客户区创建消息
3、WM_NCCALCSIZE——查询客户区尺寸
4、其它未公开的消息——这些消息应该调用DefWindowProc处理
写程序时一般通过CreateWindow(Ex)最后一个参数传入this指针,这个传入的参数一般要等到WM_NCCREATE或WM_CREATE才可以访问,实际上也是有解决办法的:
1、如果需要在处理第一次WM_NCCALCSIZE时使用this指针,可以在WM_NCCREATE而不是在WM_CREATE中获取传入的this指针并保存。注意WM_NCCREATE应该返回TRUE表示成功FALSE表示失败,而WM_CREATE返回0表示成功-1表示失败。
2、在处理WM_GETMINMAXINFO和WM_NCCALCSIZE时,如果this指针为nullptr,则直接调用DefWindowProc,可以通过这种方法跳过第一次消息。
然后可以在WM_CREATE中先获取传入的this指针并保存,再调用MoveWindow重新应用窗口大小,MoveWindow无论窗口大小的值是否改变,都会重新发送WM_GETMINMAXINFO和WM_NCCALCSIZE这两个查询消息。由于收到WM_CREATE时窗口还未显示在屏幕上,因此视觉效果是一样的。
唯一的不同是第一次WM_NCCALCSIZE参数是FALSE,RECT,而MoveWindow发送的WM_NCCALCSIZE参数是TRUE,NCCALCSIZE_PARAMS,不过实际效果并没有什么不同。
// 让系统再次查询WM_GETMINMAXINFO和WM_NCCALCSIZE
// 第一次查询是在WM_CREATE之前,但是由于需要this指针所以无法处理
RECT wrc;
GetWindowRect(hwnd, &wrc);
MoveWindow(hwnd, wrc.left, wrc.top, wrc.right - wrc.left, wrc.bottom - wrc.top, FALSE);