Mike32ab
Mike32ab

Reputation: 466

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

I want to create a window with CreateWindow() when clicking on a menu item that will be a child of the main window. I know I can use DialogBox() or CreateDialog() but I want to use CreateWindow(). I'm using this code

resource.rc file

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

About window procedure

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

Main window procedure

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

Is this a good idea? Can you register more than one class to the same instance? Also, is it a good idea to assign the main window icons to this child window or should I load them each time? Will those icons be deleted when I call UnregisterClass() in IDM_HELP? I have tried this program and everything works and the icons still show in the main window after I close this child window. But I still wonder if it's ok to assign the main window icons to this window since I call UnregisterClass() after the child window closes

Upvotes: 0

Views: 1202

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595349

There is nothing wrong with using CreateWindow/Ex() instead of CreateDialog()/DialogBox(). And there is nothing wrong with running your own modal message loop, as long as you implement it correctly. For instance, take heed of this warning:

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.

The example you showed is not doing that, so you would need to add it:

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

However, your modal window should NOT be using WM_QUIT just to break its modal loop, as doing so would exit you entire application! Use a different signal to make your modal loop break when the window is closed. For example:

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

Also, a modal window is supposed to disable its owner window, and then re-enable it when closed. Your example is not doing that either, so that needs to be added as well:

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

The Old New Thing blog has a whole series of posts on how to work with modal 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

Update: based on your comment that "No I don't want a modal window", you can ignore everything said above. All of that applies to modal windows only. Since you do not want a modal window, simply remove the secondary loop altogether and let the main message loop handle everything. Also, you do not need to call UnregisterClass(). It will unregister automatically when the process ends. Call RegisterClass() one time, either at program startup, or at least the first time you display the About window. You can use GetClassInfo/Ex() to know whether the class is already registered, or keep track of it yourself. Think of what happens if the user wants to display the About window more than one time during the process's lifetime. So let it re-use an existing class registration each time.

Try this:

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

Upvotes: 3

andlabs
andlabs

Reputation: 11578

Yes, you can use CreateWindow(). You can do anything in your event handler. Using DialogBox() just gives you a modal loop for free (so your main window cannot be interacted with until the dialog box is closed).

Yes, you can register multiple window classes. You are free to register all your window classes in advance; you do not need to call RegisterClass() and UnregisterClass() each time someone clicks your menu item.

I'm not sure if UnregisterClass() frees the various GDI resources allocated to your window class; anyone who knows the answer is free to comment.

Upvotes: 2

Related Questions