Proper way of managing controls across multiple MDI tabs

Started by Quin, April 08, 2025, 05:54:30 PM

Previous topic - Next topic

MrBcx

One more thing that should answer your earlier question regarding control ID's.

Each MDI child window is its own separate parent for its controls.
So even if every child has a control with ID 1001, Windows can still distinguish them because:

* The ID is only required to be unique within the window.

* When you send messages like GetDlgItem(hwnd, 1001), it only searches that window's
  children, such as the BCX_EDIT control in my example.

So ... there IS a silver bullet after all.    ;)

Lastly, there's nothing inherently wrong with implementing an auto-incrementing ID for each new
window's  control(s) but you will only be making work for yourself and possibly introducing bugs if
you're not managing  those ID's correctly.


MrBcx

Quin,

I don't have any experience building MDI apps but the following barebones demo might shed some light.


$ACCELERATOR hAccTable

CONST IDC_MDI_CLIENT = 100
CONST IDC_NEW_CHILD  = 101
CONST IDC_EXIT       = 102

DIM hInstance AS HINSTANCE
DIM hMDIClient AS HWND
DIM hMain AS HWND

GLOBAL  hAccTable  AS HACCEL
SET AccelTable[] AS ACCEL
    FVIRTKEY, VK_F2, IDC_NEW_CHILD
END SET


FUNCTION WinMain()
    DIM wc AS WNDCLASSEX
    DIM msg AS MSG
    DIM cc AS CLIENTCREATESTRUCT
    DIM hAccel AS HACCEL
    DIM hMenu AS HMENU
    DIM hSubMenu AS HMENU

    hInstance = GetModuleHandle(NULL)

    ' Register MDI Parent class
    wc.cbSize        = SIZEOF(WNDCLASSEX)
    wc.style         = CS_HREDRAW OR CS_VREDRAW
    wc.lpfnWndProc   = &WndProc
    wc.cbClsExtra    = 0
    wc.cbWndExtra    = 0
    wc.hInstance     = hInstance
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION)
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW)
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1)
    wc.lpszMenuName  = NULL
    wc.lpszClassName = "MDIParent"
    wc.hIconSm       = NULL
    RegisterClassEx(&wc)

    hAccTable = CreateAcceleratorTable(AccelTable, 1)

    ' Register MDI Child class
    wc.lpfnWndProc   = &ChildProc
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1)
    wc.lpszClassName = "MDIChild"
    RegisterClassEx(&wc)

    ' Create Main Menu
    hSubMenu = CreateMenu()

    $IPRINT_OFF
    AppendMenu(hSubMenu, MF_STRING, (UINT)IDC_NEW_CHILD, "&New \tF2")
    $IPRINT_ON

    AppendMenu(hSubMenu, MF_STRING, (UINT)IDC_EXIT, "E&xit")
    hMenu = CreateMenu()
    AppendMenu(hMenu, MF_POPUP, (UINT)hSubMenu, "&File")


    ' Create Main Window
    hMain = CreateWindowEx(0, "MDIParent", "MDI App with EDIT Controls", _
    WS_OVERLAPPEDWINDOW OR WS_CLIPCHILDREN, _
    CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, _
    NULL, hMenu, hInstance, NULL)

    ' Create MDI client
    cc.hWindowMenu = NULL
    cc.idFirstChild = IDC_MDI_CLIENT

    hMDIClient = CreateWindowEx(WS_EX_CLIENTEDGE, "MDICLIENT", NULL, _
    WS_CHILD OR WS_VISIBLE OR WS_CLIPCHILDREN OR WS_VSCROLL OR WS_HSCROLL, _
    0, 0, 0, 0, hMain, IDC_MDI_CLIENT, hInstance, &cc)

    ShowWindow(hMain, SW_SHOW)
    UpdateWindow(hMain)

    ' Load accelerators
    hAccel = LoadAccelerators(hInstance, "MainAccel")

    WHILE GetMessage(&msg, NULL, 0, 0)
        IF NOT TranslateMDISysAccel(hMDIClient, &msg) AND NOT TranslateAccelerator(hMain, hAccTable, &msg) THEN
            TranslateMessage(&msg)
            DispatchMessage(&msg)
        END IF
    WEND

    FUNCTION = msg.wParam
END FUNCTION

 

FUNCTION WndProc(hWnd AS HWND, msg AS UINT, wParam AS WPARAM, lParam AS LPARAM) AS LRESULT
    SELECT CASE msg
        CASE WM_COMMAND
        SELECT CASE LOWORD(wParam)
            CASE IDC_NEW_CHILD
            CreateNewMDIChild(hMDIClient)
            CASE IDC_EXIT
            PostMessage(hWnd, WM_CLOSE, 0, 0)
        END SELECT
        CASE WM_SIZE
        MoveWindow(hMDIClient, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE)
        CASE WM_DESTROY
        PostQuitMessage(0)
        CASE ELSE
        FUNCTION = DefFrameProc(hWnd, hMDIClient, msg, wParam, lParam)
        EXIT FUNCTION
    END SELECT
    FUNCTION = 0
END FUNCTION



FUNCTION ChildProc(hWnd AS HWND, msg AS UINT, wParam AS WPARAM, lParam AS LPARAM) AS LRESULT
    STATIC hEdit AS HWND

    SELECT CASE msg
        CASE WM_CREATE
        hEdit = BCX_EDIT("", hWnd, 1001, 0, 0, 220, 100)

        CASE WM_SIZE
        MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE)
        CASE ELSE
        FUNCTION = DefMDIChildProc(hWnd, msg, wParam, lParam)
        EXIT FUNCTION
    END SELECT
    FUNCTION = 0
END FUNCTION


SUB CreateNewMDIChild(hClient AS HWND)
    DIM mcs AS MDICREATESTRUCT
    mcs.szClass = "MDIChild"
    mcs.szTitle = "MDI Child"
    mcs.hOwner  = hInstance
    mcs.x = CW_USEDEFAULT
    mcs.y = CW_USEDEFAULT
    mcs.cx = 300
    mcs.cy = 200
    mcs.style = 0
    mcs.lParam = 0
    SendMessage(hClient, WM_MDICREATE, 0, &mcs)
END SUB


Quin

MrBcx,
Thanks, that's pretty much what I figured. However, I still have one unanswered question. What ID should I pass to the edit control? I have ID_EDIT defined as 300 using an enum, but even if I store the HWND and everything in a type the reuse of that ID will destroy the previous edit control. I don't know of any automatically increasing ID or anything similar in the Windows API, do I just have to store a static variable and increase it myself? not a huge deal if not, but you weren't kidding when you said everything's manual :D.

MrBcx

Quin,

Sounds like you're not missing anything.  MDI apps require that you keep track of things yourself,
there is no Windows silver bullet.  Same goes when using the TAB control, (BED is an example).  The
onus is on the coder to keep track of everything.

Quin

I'm making an application that makes heavy use of the BCX MDI functions. My question is, what's the proper way of managing the controls across these various MDI windows?
I do this in my creation handler like the docs suggest:
MsgHandler ChildCreateHandler
    Raw Edit As HWND
    Edit = Bcx_Richedit("", hWnd, ID_EDIT, 0, 0, 0, 0)
    SetWindowLongPtr(hWnd, GWL_HWNDEDIT, (LONG_PTR)Edit)
    SetWindowLongPtr(hWnd, GWL_EDITCHANGED, 0)
    SetFocus(Edit)
End Handler
But this, expectedly, breaks when you have more than one window, because multiple edit controls try to use the same ID. What am I missing here? Should I define a UDT to store the window handle, tab title, etc., then make an array of them that I populate and use whenever I need to access/store controls? Or does Windows provide a nicer method of doing this?
Thanks!