Share via

How I could add The Parent-Child relation ship into my Win32 GUI

Omar Mohamed 120 Reputation points
2026-03-25T08:02:48.7833333+00:00

Hey,
I want the users that are going to use the GUI Framework to be able to add Windows which have the Parent-Child relation ship but I want it like the winui3 style it creates a window but you still can control the parent window and when you close the parent window the child windows closes unlike the owner property which disable the owner window and Enable it when you close the owned windows
So can anyone help me

Thank you,

Windows development | Windows API - Win32

Answer accepted by question author
  1. Taki Ly (WICLOUD CORPORATION) 460 Reputation points Microsoft External Staff Moderator
    2026-03-25T10:42:32.1133333+00:00

    Hello @Omar Mohamed ,

    In Win32 there is an important distinction between a true child window and a separate top-level window. A real child window created with WS_CHILD lives inside the parent window’s client area and behaves more like a control. That is usually not the same thing as the WinUI 3-style experience you described.

    So, the Win32 concept that matches it is usually not a traditional child window. In practice, the workaround is to create a modeless secondary top-level window and let the main window manage its lifetime. That gives you the behavior of a separate window while still allowing the main window to stay interactive.

    To illustrate this approach, I put together a small Win32 sample that opens a secondary window from the main window like this:

    void CreateOwnedModelessWindow(HWND hwndOwner)
    {
        if (g_ownedWindow && IsWindow(g_ownedWindow))
        {
            ShowWindow(g_ownedWindow, SW_SHOWNORMAL);
            SetForegroundWindow(g_ownedWindow);
            DebugLog(L"[Main] Owned window already exists");
            return;
        }
        g_ownedWindow = CreateWindowExW(
            0,
            CHILD_CLASS_NAME,
            L"Owned Modeless Secondary Window",
            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
            450, 150, 500, 260,
            hwndOwner,
            nullptr,
            GetModuleHandleW(nullptr),
            nullptr
        );
        if (!g_ownedWindow)
        {
            DebugLog(L"[Main] Failed to create owned window");
            MessageBoxW(hwndOwner, L"Failed to create owned window.", L"Error", MB_ICONERROR);
            return;
        }
        DebugLog(L"[Main] Owned modeless window created");
    }
    

    With this approach, the main window remains interactive. In the sample, that can be verified because the main window continues to process commands while the secondary window is open:

    case WM_COMMAND:
    {
        if (LOWORD(wParam) == ID_BTN_OPEN)
        {
            DebugLog(L"[Main] Open button clicked");
            CreateOwnedModelessWindow(hwnd);
            return 0;
        }
        if (LOWORD(wParam) == ID_BTN_PING_MAIN)
        {
            DebugLog(L"[Main] Ping Main clicked");
            MessageBoxW(hwnd, L"Main window is still interactive.", L"Main", MB_OK);
            return 0;
        }
        if (LOWORD(wParam) == ID_BTN_STOP_LOOP)
        {
            DebugLog(L"[Main] Stop Secondary Loop clicked");
            if (g_loopRunning)
            {
                RequestStopWorker();
                MessageBoxW(hwnd, L"Stop requested for secondary loop.", L"Main", MB_OK);
            }
            else
            {
                MessageBoxW(hwnd, L"Secondary loop is not running.", L"Main", MB_OK);
            }
            return 0;
        }
        break;
    }
    

    If you also want the secondary window to keep doing work while the main window is still usable, the work should not run on the UI thread. In the sample, the secondary window starts a worker thread instead:

    DWORD WINAPI SecondaryWorkerThread(LPVOID)
    {
        DebugLog(L"[Worker] Thread started");
        g_loopRunning = true;
        g_stopRequested = false;
        int counter = 0;
        while (!g_stopRequested)
        {
            wchar_t buffer[128];
            wsprintfW(buffer, L"[Worker] Tick %d", counter++);
            DebugLog(buffer);
            Sleep(500);
        }
        DebugLog(L"[Worker] Thread stopping");
        g_loopRunning = false;
        g_workerThread = nullptr;
        return 0;
    }
    

    The secondary window can start that work without blocking either window:

    case WM_COMMAND:
    {
        if (LOWORD(wParam) == ID_BTN_CLOSE_OWNED)
        {
            DebugLog(L"[Owned] Close button clicked");
            DestroyWindow(hwnd);
            return 0;
        }
        if (LOWORD(wParam) == ID_BTN_START_LOOP)
        {
            if (!g_loopRunning && g_workerThread == nullptr)
            {
                DebugLog(L"[Owned] Start Loop clicked");
                g_workerThread = CreateThread(
                    nullptr,
                    0,
                    SecondaryWorkerThread,
                    nullptr,
                    0,
                    nullptr
                );
                if (!g_workerThread)
                {
                    DebugLog(L"[Owned] Failed to create worker thread");
                    MessageBoxW(hwnd, L"Failed to start worker thread.", L"Error", MB_ICONERROR);
                }
            }
            else
            {
                DebugLog(L"[Owned] Loop already running");
                MessageBoxW(hwnd, L"Loop is already running.", L"Owned", MB_OK);
            }
            return 0;
        }
        break;
    }
    

    The main window can also control that work:

    void RequestStopWorker()
    {
        if (g_loopRunning)
        {
            DebugLog(L"[App] Stop requested");
            g_stopRequested = true;
        }
    }
    

    To keep the lifetime relationship consistent, the sample requests the worker to stop when the main window is destroyed:

    case WM_DESTROY:
        DebugLog(L"[Main] WM_DESTROY");
        RequestStopWorker();
        PostQuitMessage(0);
        return 0;
    

    And the secondary window can also do the same when it is destroyed:

    case WM_DESTROY:
        DebugLog(L"[Owned] WM_DESTROY");
        RequestStopWorker();
        if (g_ownedWindow == hwnd)
            g_ownedWindow = nullptr;
        return 0;
    

    Also, I attached a few screenshots from my testing in case that helps illustrate more clearly

    The main window and the secondary window are both open and interactive at the same time.

    User's image

    The secondary window shows that its loop is running after clicked, confirming it can manage its own background activity.

    User's image

    The main window successfully sends a stop request to the secondary window’s loop while both windows remain open.

    User's image

    Please have a look and see whether this approach fits your framework design. If the information above is useful, you can follow this guide to give feedback.

    Thank you.


1 additional answer

Sort by: Most helpful
  1. Q&A Assist
    2026-03-25T08:03:18.09+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    In raw Win32 there are two different relationships that matter here:

    1. Parent/child (WS_CHILD)
    2. Owner/owned (top‑level windows with an owner HWND)

    From the description, the desired behavior is:

    • Child window stays usable while the parent is usable (no modal blocking).
    • Closing the parent closes the child automatically.
    • Minimizing the parent hides the child.

    That behavior matches an owned top‑level window, not a WS_CHILD child window, and not a modal dialog. In Win32 terms:

    • A child window (WS_CHILD) is clipped to the parent’s client area and is not a separate top‑level window. It is destroyed automatically when the parent is destroyed, but it does not minimize/restore independently and cannot appear outside the parent.
    • An owned window is a separate top‑level window whose owner is another top‑level window. It:
      • Always appears in front of its owner.
      • Is hidden when the owner is minimized.
      • Is destroyed when the owner is destroyed.

    This is the relationship described in the Win32 docs as owner/owned:

    When an application displays a modal dialog, the application window is the owner window, and the dialog is an owned window. An owned window always appears in front of its owner window. It is hidden when the owner is minimized, and is destroyed at the same time as the owner.

    The “modal” part (disabling the owner) is not inherent to the owner/owned relationship; it is something the dialog manager does (e.g., DialogBox disables the owner while the dialog is running). If a normal top‑level window is created with an owner and the owner is not disabled, both windows remain interactive.

    How to implement this in a Win32 GUI framework

    1. Create the parent as a normal top‑level window
      • Use CreateWindowEx with styles like WS_OVERLAPPEDWINDOW (no WS_CHILD).
    2. Create the child as an owned top‑level window
      • Also use CreateWindowEx with top‑level styles (no WS_CHILD).
      • Pass the parent’s HWND as the hWndParent parameter to CreateWindowEx.
      • Do not disable the parent.
      From the Win32 docs:

      CreateWindow … specifies the window’s parent or owner, if any…

      For an owned window, the parent parameter is the owner HWND, but the window style must be a top‑level style (no WS_CHILD).
    3. Do not use modal dialog helpers if non‑modal behavior is desired
      • Avoid DialogBox/DialogBoxParam, which disable the owner.
      • Instead, create the child/owned window with CreateWindowEx (or CreateDialog for modeless dialogs) and leave the owner enabled.
    4. Automatic destruction and hiding
      • Because the child is an owned window, the system will:
        • Hide it when the owner is minimized.
        • Destroy it when the owner is destroyed.
      • No extra code is required for that behavior.

    Relation to WinUI 3 / AppWindow

    WinUI 3’s Window and Windows App SDK’s AppWindow are abstractions over the underlying Win32 HWND model:

    • The XAML Window is backed by a Win32 application window (HWND).
    • AppWindow is a high‑level abstraction with a 1:1 mapping to a top‑level HWND.

    The parent/child/owner relationships in WinUI 3 ultimately map to the same Win32 concepts described above.

    To emulate “WinUI 3 style” multiple windows in a custom Win32 framework, expose an API that:

    • Creates a new top‑level window.
    • Internally calls CreateWindowEx with the owner HWND set to the parent window’s HWND.
    • Does not disable the parent.

    This yields:

    • Independent, interactive windows.
    • Automatic hide on parent minimize.
    • Automatic destruction on parent close.

    References:

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.