1
votes

I know, there must be a way to embed an (modeless) dialog as child of a window created with CreateWindow. In my case I want to embed them into an scroll-able container window, whereby this container windows it self is a child of the main window (see picture).

Embedded Dialogs

The first problem that I encounter is, that I still want be able to use TAB keys and other dialog specific navigation. But how?

My message loop:

while (GetMessage(&msg, NULL, 0, 0)) {
    if (IsDialogMessage(msg.hwnd, &msg)) continue;
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

Edit: For testing purpose I modified the loop:

while (GetMessage(&msg, NULL, 0, 0)) {
    if (IsEmbeddedDialogWindow(msg.hwnd)) {
        if (IsDialogMessage(msg.hwnd, &msg)) continue;
    }
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

while (GetMessage(&msg, NULL, 0, 0)) {
    if (IsScrollableContainerWindow(msg.hwnd)) {
        if (IsDialogMessage(msg.hwnd, &msg)) continue;
    }
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

More information how to make it the right way can be found here: Using the TAB key to navigate in non-dialogs, redux

With this message loop, nothing happens if I want to enter some dialog texts as if the message is not handled. If IsDialogMessage is removed, I can enter some text into an edit control in one of the embedded dialogs, however dialog navigation doesn't work as intended. Of course, WS_TABSTOP style is set for dialog child controls.

The scroll-able container is created with CreateWindowEx with styles WS_CHILD, WS_VISIBLE, WS_VSCROLL, WS_TABSTOP, WS_EX_CONTROLPARENT and dialogs are created as children of this container.

    HWND hWndContainer = GroupBarPanelCreate(WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, WS_EX_CONTROLPARENT, hWndMain, 0, 0, 400, 400);
    GROUPBAR_PANEL* GroupBarPanel = (GROUPBAR_PANEL*) GetWindowLongPtr(hWndContainer, 0);
    // Test embedding dialogs
    for (unsigned int i = 0; i < 10; i++) {
        HWND hWndDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWndContainer, About, 0);
        GroupBarPanelInternalAddLast(GroupBarPanel, hWndChild, hWndDlg, nullptr);
    }

My GroupBarPanelInternalAddLast modify modeless dialog styles by removing caption and borders, and ensures that WS_CHILD, WS_VISIBLE and WS_TABSTOP is set (SetWindowLong(hWndDlg, GWL_STYLE, ...)). (Also tested WS_EX_CONTROLPARENT style) With SetParent(hWndDlg, hWndContainer) modeless dialogs parent is changed.

Working Demo

So what am I missing here? As I found out, neither the container window procedure nor the embedded (for testing purpose subclassed) dialog procedure almost never gets WM_SETFOCUS or WM_KILLFOCUS messages for example, but why that?

Solution: Calling IsDialogMessage for the top level window.

while (GetMessage(&msg, NULL, 0, 0)) {
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
        if (IsDialogMessage(hWndTopLevel, &msg)) {
            continue;
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
1
IsDialogMessage needs to be passed the HWND of the candidate dialog, not the window the message is targeted at.Jonathan Potter
Well, it should be given the HWND of the window that has the WS_EX_CONTROLPARENT style, and which is the parent of the other dialogs.Chris Becke
On creation you need to manually set the input focus, otherwise keyboard navigation won't work. I'm not entirely convinced that this can be made to work reliably. Why aren't you creating regular windows instead?IInspectable
@JonathanPotter It also didn't work. I tested both, IsDialogMessage was only called for the container window, and I tested also to call it for the dialog windows. Both didn't work.bkausbk
Er oh, your IsDialogMessage() call is still wrong. You don't want to call IsDialogMessage() only when the message is sent to the dialog. You want to call it for all messages, passing the HWND of the topmost window that contains the sub-dialogs — in your case, of the Win32AppDemo1 window.andlabs

1 Answers

2
votes

You are not using IsDialogMessage() correctly, and you said you learned from some bad tutorials. I don't know which tutorials you saw, so I can't tell you what they got wrong; all I can do is say how to use it correctly.

IsDialogMessage() takes two parameters: the message itself, and the window handle of the toplevel window. This bit in bold is the important part: the IsDialogMessage() function needs to know which dialog to work with in the event of tab navigation or Enter/Esc handling.

You don't want to pass msg.hwnd; that's the control itself.

And in the case of nested child dialogs like you have here, you don't want to pass in the child dialog's handle; that would confine IsDialogMessage() to that dialog.

So in your screenshot, you want to pass in the handle of the main window, that is, the window called Win32ApiDemo1.

Also make sure all the child dialogs and your custom expander control have WS_EX_CONTROLPARENT so tab navigation can recurse.