单击菜单命令时使用 createwindow() 创建 window

creating a window with createwindow() when clicking a menu command

我想在单击将成为主 window 的 child 的菜单项时使用 CreateWindow() 创建一个 window。我知道我可以使用 DialogBox() 或 CreateDialog(),但我想使用 CreateWindow()。我正在使用此代码

resource.rc 文件

#include "resource.h"
IDM_MENU MENU
{
    POPUP "&Help"
    {
        MENUITEM "&About", IDM_HELP
    }
}

关于window程序

LRESULT CALLBACK AboutProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

主要window程序

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case IDM_HELP:
            {
                WNDCLASSEX wc;
                HWND hDlg;
                MSG msg;
                SecureZeroMemory(&wc, sizeof(WNDCLASSEX));
                wc.cbSize = sizeof(WNDCLASSEX);
                wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
                wc.hCursor = LoadCursor(0, IDC_ARROW);
                wc.hIcon = (HICON)GetClassLong(hwnd, GCL_HICON);
                wc.hIconSm = (HICON)GetClassLong(hwnd, GCL_HICONSM);
                wc.hInstance = GetModuleHandle(0);
                wc.lpfnWndProc = AboutProc;
                wc.lpszClassName = TEXT("AboutClass");          

                if(!RegisterClassEx(&wc))
                    break;

                hDlg = CreateWindowEx(0, wc.lpszClassName, TEXT("About"), WS_OVERLAPPEDWINDOW, 0, 0, 300, 200, hwnd, 0, wc.hInstance, 0);

                ShowWindow(hDlg, SW_SHOWNORMAL);

                while(GetMessage(&msg, 0, 0, 0) > 0)
                {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
                UnregisterClass(wc.lpszClassName, wc.hInstance);
            }
            break;
        }
        break;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

这是个好主意吗?您可以向同一个实例注册多个 class 吗?此外,将主要 window 图标分配给此 child window 是个好主意还是我应该每次都加载它们?当我在 IDM_HELP 中调用 UnregisterClass() 时,这些图标会被删除吗?我试过这个程序,一切正常,在我关闭这个 child window 后,图标仍然显示在主 window 中。但是我仍然想知道是否可以将主要的 window 图标分配给这个 window 因为我在 child window 关闭

之后调用了 UnregisterClass()

是的,您可以使用 CreateWindow()。您可以在事件处理程序中做 任何事情。使用 DialogBox() 只是免费给你一个模态循环(所以你的主 window 在对话框关闭之前不能与之交互)。

是的,您可以注册多个 window class。您可以自由地提前注册所有 window classes;您不需要在每次有人点击您的菜单项时调用 RegisterClass()UnregisterClass()

我不确定 UnregisterClass() 是否释放了分配给您 window class 的各种 GDI 资源;知道答案的小伙伴可以自由评论。

CreateWindow/Ex()代替CreateDialog()/DialogBox()没有错。 运行 你自己的模态消息循环也没有错,只要你 正确地 实现它。例如,注意这个警告:

Modality, part 3: The WM_QUIT message

The other important thing about modality is that a WM_QUIT message always breaks the modal loop. Remember this in your own modal loops! If ever you call the PeekMessage function or the GetMessage function and get a WM_QUIT message, you must not only exit your modal loop, but you must also re-generate the WM_QUIT message (via the PostQuitMessage message) so the next outer layer will see the WM_QUIT message and do its cleanup as well. If you fail to propagate the message, the next outer layer will not know that it needs to quit, and the program will seem to "get stuck" in its shutdown code, forcing the user to terminate the process the hard way.

您展示的示例没有这样做,因此您需要添加它:

ShowWindow(hDlg, SW_SHOWNORMAL);

do
{
    BOOL bRet = GetMessage(&msg, 0, 0, 0);
    if (bRet > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
    {
        if (bRet == 0) 
            PostQuitMessage(msg.wParam); // <-- add this!
        break;
    }
}
while (1);

UnregisterClass(wc.lpszClassName, wc.hInstance);

但是,您的模态 window 不应该使用 WM_QUIT 只是为了打破它的模态循环,因为这样做会退出您的整个应用程序!当 window 关闭时,使用不同的信号使模态循环中断。例如:

ShowWindow(hDlg, SW_SHOWNORMAL);

while (IsWindow(hDlg) && (GetMessage(&msg, 0, 0, 0) > 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

LRESULT CALLBACK AboutProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

此外,模式 window 应该禁用其所有者 window,然后在关闭时重新启用它。您的示例也没有这样做,因此也需要添加:

ShowWindow(hDlg, SW_SHOWNORMAL);
EnableWindow(hwnd, FALSE); // <-- add this
...

LRESULT CALLBACK AboutProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_CLOSE:
        EnableWindow(GetWindow(hwnd, GW_OWNER), TRUE); // <-- add this
        DestroyWindow(hwnd);
        break;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

Old New Thing 博客有一系列关于如何使用模式 windows 的文章。

Modality, part 1: UI-modality vs code-modality

Modality, part 2: Code-modality vs UI-modality

Modality, part 3: The WM_QUIT message

Modality, part 4: The importance of setting the correct owner for modal UI

Modality, part 5: Setting the correct owner for modal UI

Modality, part 6: Interacting with a program that has gone modal

Modality, part 7: A timed MessageBox, the cheap version

Modality, part 8: A timed MessageBox, the better version

Modality, part 9: Setting the correct owner for modal UI, practical exam

The correct order for disabling and enabling windows

Make sure you disable the correct window for modal UI

更新:根据您的评论"No I don't want a modal window",您可以忽略上述所有内容。所有这些仅适用于模态 windows。由于您不需要模态 window,只需完全删除辅助循环并让主消息循环处理所有内容。此外,您不需要调用 UnregisterClass()。当进程结束时,它将自动注销。调用 RegisterClass() 一次,可以在程序启动时调用,或者至少在您第一次显示关于 window 时调用。您可以使用 GetClassInfo/Ex() 来了解 class 是否已经注册,或者您自己跟踪它。想一想如果用户想要在进程的生命周期内多次显示 About window 会发生什么。所以让它每次都重新使用现有的 class 注册。

试试这个:

LRESULT CALLBACK AboutProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;

        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDM_HELP:
                {
                    WNDCLASSEX wc = {0};
                    wc.cbSize = sizeof(WNDCLASSEX);
                    wc.hInstance = GetModuleHandle(0);
                    wc.lpszClassName = TEXT("AboutClass");          

                    if (!GetClassInfoEx(wc.hInstance, wc.lpszClassName, &wc))
                    {
                        wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
                        wc.hCursor = LoadCursor(0, IDC_ARROW);
                        wc.hIcon = (HICON)GetClassLong(hwnd, GCL_HICON);
                        wc.hIconSm = (HICON)GetClassLong(hwnd, GCL_HICONSM);
                        wc.lpfnWndProc = AboutProc;

                        if (!RegisterClassEx(&wc))
                            break;
                    }

                    HWND hDlg = CreateWindowEx(0, wc.lpszClassName, TEXT("About"), WS_OVERLAPPEDWINDOW, 0, 0, 300, 200, hwnd, 0, wc.hInstance, 0);

                    if (hDlg)
                        ShowWindow(hDlg, SW_SHOWNORMAL);
                }
                break;
            }
            break;

        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}