【问题标题】:Why are MDI child windows non-existent after WM_NCCREATE?为什么在 WM_NCCREATE 之后 MDI 子窗口不存在?
【发布时间】:2019-10-25 18:57:29
【问题描述】:

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

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

resource.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

这是resource.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; }
}

ControllerGL 类

#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);
}

main.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. 我没有收到任何错误,附加调试器也没有显示任何内容。

有人遇到过这个问题吗?

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

抱歉,代码太多了。老实说,我确实尝试过修剪它。

【问题讨论】:

  • 您没有显示任何注册类、创建父窗口或创建子窗口的代码。请提供minimal reproducible example。并且不相关,但ctrlMDIChildWndProc() 中不需要是static
  • 在收到 WM_MDICREATE 消息之前,您无法获得 MDICREATESTRUCT*。
  • Remy Lebeau 最小样本足够大,我敢肯定,但它确实存在。谢谢。
  • @HansPassant WM_MDICREATE 由应用程序发送到 MDI 客户端窗口,而不是由操作系统发送到子窗口。但是WM_MDICREATE根本没有在这段代码中使用,所以没有MDICREATESTRUCTavailable。

标签: c++ winapi


【解决方案1】:

我发现您的代码中至少有 2 个需要修复的主要错误:

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

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

【讨论】:

  • 我的理解是,当 wndExStyle 设置为 WS_EX_MDICHILD 时,CreateWindowEx 会发送一个指向 MDICREATESTRUCT 的指针。我确实尝试使用带有指向 MDICREATESTRUCT 和 CreateMDIWindow 的指针的 SENDMESSAGE,它们都运行良好,直到我在窗口过程中尝试 NC_CREATE。至于你的第二点;是的,我是个白痴。除了让 ControllerGL 类全局化之外,还有什么更优雅的方法吗?
  • 这对我太粗鲁了,但我没有感谢你的时间,所以非常感谢。
  • 1) MDICREATESTRUCT 仅在MDICREATESTRUCT 被发送到CreateWindow/Ex() 时被发送到WM_(NC)CREATE。使用 WM_MDICREATECreateMDIWindow() 在内部为您处理。您的 CreateWindowEx() 代码不会这样做。由于WS_EX_MDICHILD 的文档记录很少,并且在实践中很少使用,我建议您改用WM_MDICREATECreateMDIWindow()。如果您在使用它们时仍然遇到问题,请更新您的问题以显示该代码; 2) 使用newdelete 动态创建/销毁ControllerGL 对象。您仍然可以将指针存储在HWND 中。
  • CreateWindowEx、WM_MDICreate 和 CreateMDIWindow 方法都可以正常工作,就像它们应该做的那样。我坚持使用 CreateWindowEx 的唯一原因是因为我前段时间在线阅读了一个教程,其中提到了多线程 mdi 子窗口,说这是不可能的(?)使用 WM_MDICREATE,但现在我找它我找不到它,或记住它是谁或是什么。
  • @BenWeston 您没有在工作线程中创建 MDI 子窗口,因此选择使用 CreateWindowEx() 而不是 WM_MDICHILD/CreateMDIWindow() 是一个有争议的问题。无论如何,请参阅 MSDN 上的 Using the Multiple Document Interface: Creating a Child Window。工作线程可以使用CreateMDIWindow() 而不是CreateWindowEx()。只有WM_MDICHILD 不能跨线程边界使用。
【解决方案2】:

嗯,这太丢人了。我在本地声明了 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 的 cmets 和回答,如果其他人有不同/更好的方法,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-06
    • 1970-01-01
    相关资源
    最近更新 更多