为什么 MDI 子窗口在WM_NCCREATE后不存在?



我正在使用OpenGL编写MDI图形应用程序。我有一个基于类Controller的类ControllerGL,它将具有绘制到MDI子级的方法(其中一些需要自己的线程)。创建子窗口时,我将WNDCLASSEXcbWndExtra设置为sizeof(Win::Controller*)并使用Set/GetWindowLongPtr()检索指向子窗口过程WM_NCCREACTEControllerGL类的指针。

我有这个为 SDI 工作,所以我很确定用于设置/检索指向ControllerGL类的指针的所有代码都很好,并且在 MDI 应用程序中显示主窗口,我从 MDI 子窗口过程的WM_NCCREATE函数中获取ControllerGL类的功能实例。

资源.rc 文件中的菜单和字符串表:

IDM_MDI MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New",                        ID_FILE_NEW
END
POPUP "&Window",
BEGIN
MENUITEM "&Cascade",                    ID_WINDOW_CASCADE
MENUITEM "Tile &Horizontal",            ID_WINDOW_TILEHORIZONTAL
MENUITEM "Tile &Vertical",              ID_WINDOW_TILEVERTICAL
MENUITEM "Arrange &Icons",              ID_WINDOW_ARRANGEICONS
END
END
STRINGTABLE
BEGIN
IDS_MDI_TITLE           "Win32 MDI"
IDS_MDI_CLASSNAME       "MDIMAIN"
IDS_MDICHILD_TITLE      "MDIChild"
IDS_MDICHILD_CLASSNAME  "MDICHILD"
END

这是针对资源.h 文件

#define IDI_MDI                         101
#define IDI_MDI_SMALL                   102
#define IDI_MDICHILD                    103
#define IDI_MDICHILD_SMALL              104
#define IDS_MDI_TITLE                   105
#define IDS_MDI_CLASSNAME               106
#define IDS_MDICHILD_TITLE              107
#define IDS_MDICHILD_CLASSNAME          108
#define IDM_MDI                         109
#define ID_FILE_NEW                     110
#define ID_WINDOW_CASCADE               111
#define ID_WINDOW_TILEHORIZONTAL        112
#define ID_WINDOW_TILEVERTICAL          113
#define ID_WINDOW_ARRANGEICONS          114
#define IDC_MDICHILD_FIRST              50000

ControllerGL 类的 ViewGL 和 ModelGL 类:

namespace Win
{
class ViewGL
{
public:
ViewGL();
~ViewGL();
};
}
class ModelGL
{
public:
ModelGL();
~ModelGL();
};

控制器类:

#include <windows.h>
namespace Win
{
class Controller
{
public:
Controller();
virtual ~Controller ();
void setHandle(HWND handle);
virtual int close();
virtual int create();
virtual int destroy();
protected:
HWND handle;
};
inline void Controller::setHandle(HWND hWnd) { handle = hWnd; }
inline int Controller::close() { ::DestroyWindow(handle); return 0; }
inline int Controller::create() { return 0; }
inline int Controller::destroy() { return 0; }
}

控制器 GL 类

#include "Controller.h"
#include "ViewGL.h"
#include "ModelGL.h"
namespace Win
{
class ControllerGL : public Controller
{
public:
ControllerGL(ModelGL* model, ViewGL* view);
~ControllerGL() {};
private:
ModelGL* modelGL;
ViewGL* viewGL;
};
}

过程.h

#include <windows.h>
namespace Win
{
LRESULT CALLBACK MDIChildWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
}

程序.cpp

#include "procedure.h"
#include "Controller.h"
LRESULT CALLBACK Win::MDIChildWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT returnValue = 0;
Controller *ctrl;
if (uMsg == WM_NCCREATE)
{
CREATESTRUCT* pCreate = (CREATESTRUCT*)(lParam);
MDICREATESTRUCT* pMdiCreate = (MDICREATESTRUCT*)pCreate->lpCreateParams;
ctrl = (Controller*)pMdiCreate->lParam;
SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ctrl));
ctrl->setHandle(hWnd);
SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
else
ctrl = reinterpret_cast<Controller *>(GetWindowLongPtr(hWnd, 0));
if (!ctrl)
return DefMDIChildProc(hWnd, uMsg, wParam, lParam);
switch (uMsg)
{
case WM_CREATE:
//returnValue = ctrl->create(); For when ControllerGL works properly
break;
case WM_CLOSE:
//returnValue = ctrl->close();
break;
case WM_DESTROY:
//returnValue = ctrl->destroy();
break;
}
//return returnvalue;
return DefMDIChildProc(hWnd, uMsg, wParam, lParam);
}

主.cpp

#include <windows.h>
#include "ControllerGL.h"
#include "ModelGL.h"
#include "ViewGL.h"
#include "procedure.h"
#include "resource.h"
#define MAX_LOADSTRING 100
char g_szMDIChild_Title[MAX_LOADSTRING];
char g_szMDIChild_ClassName[MAX_LOADSTRING];
static HINSTANCE g_hInst;
static HWND g_hMDI;
static HWND g_hMDIClient;
static LRESULT CALLBACK MDIWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg)
{
case WM_CREATE:
{
DragAcceptFiles(hWnd, TRUE);
CLIENTCREATESTRUCT ccs;
ccs.hWindowMenu = GetSubMenu(GetMenu(hWnd), 1);
ccs.idFirstChild = IDC_MDICHILD_FIRST;
g_hMDIClient = CreateWindowEx(0,
"mdiclient",
NULL,
WS_VISIBLE | WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
hWnd,
0,
g_hInst,
(void*)(&ccs));
if (!g_hMDIClient)
MessageBox(hWnd, "Could not create MDI client!", "Error!", MB_OK | MB_ICONERROR);
return 0;
}
break;
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
{
switch LOWORD(wParam)
{
case ID_FILE_NEW:
{
ModelGL model;
Win::ViewGL view;
Win::ControllerGL glCtrl(&model, &view);
HWND hChild = CreateWindowEx(WS_EX_MDICHILD,
g_szMDIChild_ClassName,
g_szMDIChild_Title,
WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
g_hMDIClient,
NULL,
g_hInst,
(LPVOID)&glCtrl);
}
break;
case ID_WINDOW_CASCADE:
PostMessage(g_hMDIClient, WM_MDICASCADE, 0, 0);
break;
case ID_WINDOW_TILEHORIZONTAL:
PostMessage(g_hMDIClient, WM_MDITILE, MDITILE_HORIZONTAL, 0);
break;
case ID_WINDOW_TILEVERTICAL:
PostMessage(g_hMDIClient, WM_MDITILE, MDITILE_VERTICAL, 0);
break;
case ID_WINDOW_ARRANGEICONS:
PostMessage(g_hMDIClient, WM_MDIICONARRANGE, 0, 0);
break;
default:
{
if (LOWORD(wParam) >= IDC_MDICHILD_FIRST)
{
DefFrameProc(hWnd, g_hMDIClient, uMsg, wParam, lParam);
}
else
{
HWND hChild;
hChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE, 0, 0);
if (hChild)
{
SendMessage(hChild, WM_COMMAND, wParam, lParam);
}
}
}
};
}
break;
default:
return DefFrameProc(hWnd, g_hMDIClient, uMsg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInst);
UNREFERENCED_PARAMETER(lpCmdLine);
char g_szMDI_Title[MAX_LOADSTRING];
char g_szMDI_ClassName[MAX_LOADSTRING];
LoadString(hInst, IDS_MDI_TITLE, g_szMDI_Title, MAX_LOADSTRING);
LoadString(hInst, IDS_MDI_CLASSNAME, g_szMDI_ClassName, MAX_LOADSTRING);
LoadString(hInst, IDS_MDICHILD_TITLE, g_szMDIChild_Title, MAX_LOADSTRING);
LoadString(hInst, IDS_MDICHILD_CLASSNAME, g_szMDIChild_ClassName, MAX_LOADSTRING);
HACCEL hAccelTable = LoadAccelerators(hInst, MAKEINTRESOURCE(IDI_MDI));
WNDCLASSEX wcex;
ZeroMemory(&wcex, sizeof(WNDCLASSEX));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = Win::MDIChildWndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(Win::Controller*);
wcex.hInstance = hInst;
wcex.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_MDICHILD));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = g_szMDIChild_ClassName;
wcex.hIconSm = LoadIcon(hInst, MAKEINTRESOURCE(IDI_MDICHILD_SMALL));
if (!RegisterClassEx(&wcex))
{
MessageBox(0, "Failed to create MDI child window class", "Error!", MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ZeroMemory(&wcex, sizeof(WNDCLASSEX));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = MDIWndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInst;
wcex.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_MDI));
wcex.hCursor = LoadCursor(0, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wcex.lpszMenuName = MAKEINTRESOURCE(IDM_MDI);
wcex.lpszClassName = g_szMDI_ClassName;
wcex.hIconSm = LoadIcon(hInst, MAKEINTRESOURCE(IDI_MDI_SMALL));
if (!RegisterClassEx(&wcex))
{
MessageBox(0, "Failed to create MDI main window class", "Error!", MB_ICONEXCLAMATION | MB_OK);
return 0;
}
g_hMDI = CreateWindowEx(0,
g_szMDI_ClassName,
g_szMDI_Title,
WS_VISIBLE | WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
hInst,
NULL);
if (g_hMDI == NULL)
{
MessageBox(NULL, "Failed to create MDI main window!", "Error!", MB_ICONEXCLAMATION | MB_OK);
return 0;
}
g_hInst = hInst;
MSG uMsg;
while (GetMessage(&uMsg, 0, 0, 0)) {
if (!TranslateMDISysAccel(g_hMDIClient, &uMsg) &&
!TranslateAccelerator(uMsg.hwnd, hAccelTable, &uMsg))
{
TranslateMessage(&uMsg);
DispatchMessage(&uMsg);
}
};
return (int)uMsg.wParam;
}

使用 FILE>NEW 打开新文档时:

  1. 所有子窗口都不可见。

  2. 只有第一个子窗口在主窗体的 Windows 菜单中获取一个条目,创建任何更多窗口都会删除此条目。

  3. 我没有收到任何错误,附加调试器不会显示任何内容。

有人遇到过这个问题吗?

或者这不是一个问题,而是一个哥伦布蛋,而我错过了一些明显的东西?

对不起,代码堆积如山。老实说,我确实试图修剪它。

我在您的代码中看到至少 2 个需要修复的主要错误:

  1. WM_MDICREATE是应用发送到 MDI 客户端窗口以创建 MDI 子窗口的消息。子 WndProc 将收到WM_(NC)CREATE条消息,其中包含在WM_MDICREATElParam中传递的MDICREATESTRUCT*指针(如果使用CreateMDIWindow()而不是WM_MDICREATE,它将为您创建必要的MDICREATESTRUCT)。但是,您的代码不使用WM_MDICREATE(或CreateMDIWindow())来创建其 MDI 子窗口,而是直接使用CreateWindowEx(WS_EX_MDICHILD, ...),并且没有在lpParam参数中传递MDICREATESTRUCT*,而是传递ControllerGL*指针。因此,在孩子的 WndProcWM_(NC)CREATE处理程序中,CREATESTRUCT::lpCreateParam字段将是ControllerGL*指针,而不是MDICREATESTRUCT*指针。

  2. ID_FILE_NEW处理程序传递一个指向本地ControllerGL对象的CreateWindowEx()指针,该指针超出范围,并在CreateWindowEx()退出后销毁,使新创建的 MDI 子窗口具有悬空的Controller*指针,指向所有后续消息的无效内存。您需要通过new动态分配ControllerGL对象,然后在销毁 MDI 子窗口时(例如在其WM_(NC)DESTROY处理程序中)delete它。

嗯,这太丢脸了。我在本地声明了 ControllerGL 类,并在 CreateWindowEx() 退出时销毁,使 MDI 子窗口带有指向不存在的 ControllerGL 类实例的指针。我应该这样做;在 main 函数的开头创建一个指向 ModelGL、ViewGL 和 ControllerGL 类的指针:

#include <windows.h>
#include "ControllerGL.h"
#include "ModelGL.h"
#include "ViewGL.h"
#include "procedure.h"
#include "resource.h"
#define MAX_LOADSTRING 100
char g_szMDIChild_Title[MAX_LOADSTRING];
char g_szMDIChild_ClassName[MAX_LOADSTRING];
static HINSTANCE g_hInst;
static HWND g_hMDI;
static HWND g_hMDIClient;
ModelGL *model;
Win::ViewGL *view;
Win::ControllerGL *glCtrl;
//The rest of the main function remains unchanged

并在ID_FILE_NEW处理程序中声明每个的新实例,不要忘记从变量 glCtrl 中删除与号,该变量已经声明为指针:

model = new ModelGL();
view = new Win::ViewGL();
glCtrl = new Win::ControllerGL(model, view);
HWND hChild = CreateWindowEx(WS_EX_MDICHILD,
g_szMDIChild_ClassName,
g_szMDIChild_Title,
WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
rcClient.right,
rcClient.bottom,
g_hMDIClient,
NULL,
g_hInst,
(LPVOID)glCtrl);
If (!hWnd)
{
delete model; model = NULL;
delete view; view = NULL;
delete glCtrl; glCtrl= NULL;
}

非常感谢Remy Lebeau的评论和回答,如果其他人有不同/更好的方法,请告诉我。

最新更新