A template to demonstrate CWindow's MDI support. To avoid bloat in applications that don't need it, I have wrapped all the MDI code in CWindod with #ifdef USEMDI. Therefore, to use it you have to add #define USEMDI before the #INCLUDEs.
' ########################################################################################
' Microsoft Windows
' File: CW_MDI.pbtpl
' Contents: Template - CWindow MDI Framwwork (High DPI)
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2015 Jose Roca. Freeware. Use at your own risk.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ########################################################################################
#define unicode
#define USEMDI
#INCLUDE ONCE "windows.bi"
#INCLUDE ONCE "Afx/CWindow.inc"
#INCLUDE ONCE "Afx/AfxWin.inc"
USING Afx.CWindowClass
' // Edit control identifier
#define IDC_EDIT 101
' // Menu identifiers
#define IDM_NEW 1001 ' New file
#define IDM_OPEN 1002 ' Open file...
#define IDM_SAVE 1003 ' Save file
#define IDM_SAVEAS 1004 ' Save file as...
#define IDM_EXIT 1005 ' Exit
#define IDM_UNDO 2001 ' Undo
#define IDM_CUT 2002 ' Cut
#define IDM_COPY 2003 ' Copy
#define IDM_PASTE 2004 ' Paste
#define IDM_TILEH 3001 ' Tile hosizontal
#define IDM_TILEV 3002 ' Tile vertical
#define IDM_CASCADE 3003 ' Cascade
#define IDM_ARRANGE 3004 ' Arrange icons
#define IDM_CLOSE 3005 ' Close
#define IDM_CLOSEALL 3006 ' Close all
DECLARE FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
END WinMain(GetModuleHandleW(""), NULL, COMMAND(), SW_NORMAL)
' ========================================================================================
' Build the menu
' ========================================================================================
FUNCTION BuildMenu () AS HMENU
DIM hMenu AS HMENU
DIM hPopUpMenu AS HMENU
hMenu = CreateMenu
hPopUpMenu = CreatePopUpMenu
AppendMenuW hMenu, MF_POPUP OR MF_ENABLED, CAST(UINT_PTR, hPopUpMenu), "&File"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_NEW, "&New" & CHR(9) & "Ctrl+N"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_OPEN, "&Open..." & CHR(9) & "Ctrl+O"
AppendMenuW hPopUpMenu, MF_SEPARATOR, 0, ""
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_SAVE, "&Save" & CHR(9) & "Ctrl+S"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_SAVEAS, "Save &As..."
AppendMenuW hPopUpMenu, MF_SEPARATOR, 0, ""
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_EXIT, "E&xit" & CHR(9) & "Alt+F4"
hPopUpMenu = CreatePopUpMenu
AppendMenuW hMenu, MF_POPUP OR MF_ENABLED, CAST(UINT_PTR, hPopUpMenu), "&Edit"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_UNDO, "&Undo" & CHR(9) & "Ctrl+Z"
AppendMenuW hPopUpMenu, MF_SEPARATOR, 0, ""
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_CUT, "Cu&t" & CHR(9) & "Ctrl+X"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_COPY, "&Copy" & CHR(9) & "Ctrl+C"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_PASTE, "&Paste" & CHR(9) & "Ctrl+V"
hPopUpMenu = CreatePopUpMenu
AppendMenuW hMenu, MF_POPUP OR MF_ENABLED, CAST(UINT_PTR, hPopUpMenu), "&Window"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_TILEH, "&Tile Horizontal"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_TILEV, "Tile &Vertical"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_CASCADE, "Ca&scade"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_ARRANGE, "&Arrange &Icons"
AppendMenuW hPopUpMenu, MF_SEPARATOR, 0, ""
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_CLOSE, "&Close" & CHR(9) & "Ctrl+F4"
AppendMenuW hPopUpMenu, MF_ENABLED, IDM_CLOSEALL, "Close &All"
FUNCTION = hMenu
END FUNCTION
' ========================================================================================
' ========================================================================================
' Default CWindow MDI callback function.
' ========================================================================================
FUNCTION MDIWindowProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
DIM hEdit AS HWND
DIM rc AS RECT
SELECT CASE uMsg
CASE WM_CREATE
' // Retrieve a reference to the CWindow class from the main window handle
DIM hRootOwner AS HWND = GetAncestor(hwnd, GA_ROOTOWNER)
DIM pWindow AS CWindow PTR
IF hRootOwner THEN pWindow = CAST(CWindow PTR, GetPropW(hRootOwner, "CWINDOWPTR"))
' // Create and edit control
IF pWindow THEN
GetClientRect hwnd, @rc
pWindow->AddControl("Edit", hwnd, IDC_EDIT, "", 0, 0, rc.Right, rc.Bottom, _
WS_CHILD OR WS_VISIBLE OR ES_MULTILINE OR WS_VSCROLL OR WS_HSCROLL OR ES_AUTOHSCROLL OR ES_AUTOVSCROLL OR ES_WANTRETURN OR ES_NOHIDESEL)
EXIT FUNCTION
END IF
CASE WM_MDIACTIVATE
IF lParam = hwnd THEN
END IF
EXIT FUNCTION
CASE WM_SETFOCUS
' // Set the keyboard focus to the first control that is
' // visible, not disabled, and has the WS_TABSTOP style
SetFocus GetNextDlgTabItem(hwnd, NULL, FALSE)
CASE WM_SIZE
IF wParam <> SIZE_MINIMIZED THEN
' // Resize the window and/or its controls
hEdit = GetDlgItem(hwnd, IDC_EDIT)
MoveWindow hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE
END IF
' Don't exit. Let DefMDIChildProc to process the message for
' properly resizing of the MDI child window.
CASE WM_DESTROY
' // Do cleanup if needed, such removing properties attached
' // to the MDI child window.
EXIT FUNCTION
END SELECT
' // The DefMDIChildProc function provides default processing for any window
' // message that the window procedure of a multiple-document interface (MDI)
' // child window does not process. A window message not processed by the window
' // procedure must be passed to the DefMDIChildProc function, not to the
' // DefWindowProc function.
FUNCTION = DefMDIChildProc(hwnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Window procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hWnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
STATIC hwndClient AS HWND ' // Handle of the MDI client window
DIM hwndActive AS HWND ' // Active window
DIM ptnmhdr AS NMHDR PTR ' // Information about a notification message
DIM hMdi AS HWND ' // MDI child window handle
STATIC nIdx AS LONG ' // Counter
STATIC pWindow AS CWindow PTR
FUNCTION = 0
' // Retrieve the MDI client window handle
IF hwndClient = NULL AND pWindow <> NULL THEN hwndClient = pWindow->hwndClient
SELECT CASE AS CONST uMsg
CASE WM_CREATE
' // Retrieve a reference to the CWindow class from the CREATESTRUCT structure
DIM pCreateStruct AS CREATESTRUCT PTR = CAST(CREATESTRUCT PTR, lParam)
pWindow = CAST(CWindow PTR, pCreateStruct->lpCreateParams)
EXIT FUNCTION
CASE WM_COMMAND
SELECT CASE LOWORD(wParam)
CASE IDCANCEL
IF HIWORD(wParam) = BN_CLICKED THEN
SendMessageW hwnd, WM_CLOSE, 0, 0
EXIT FUNCTION
END IF
' // New window
CASE IDM_NEW
IF hwndClient THEN
nIdx += 1
DIM mdi AS MDICREATESTRUCTW, dwStyle AS DWORD
IF IsZoomed(CAST(HWND, SendMessageW(hwndClient, WM_MDIGETACTIVE, 0, 0))) THEN dwStyle = WS_MAXIMIZE
IF LOBYTE( GetVersion) < 4 THEN
mdi.szClass = CAST(LPCWSTR, @"FBFrameClass")
mdi.szTitle = NULL
mdi.hOwner = CAST(HANDLE, GetWindowLongPtrW(hwndClient, GWLP_HINSTANCE))
mdi.x = CW_USEDEFAULT
mdi.y = CW_USEDEFAULT
mdi.cx = CW_USEDEFAULT
mdi.cy = CW_USEDEFAULT
mdi.Style = dwStyle
hMdi = CAST(HWND, SendMessageW(hwndClient, WM_MDICREATE, 0, CAST(LPARAM, @mdi)))
ELSE
hMdi = CreateWindowExW(WS_EX_MDICHILD OR WS_EX_CLIENTEDGE, _
"FBFrameClass", _
"", _
dwStyle, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
hwndClient, _
NULL, _
CAST(HANDLE, GetWindowLongPtrW(hwndClient, GWLP_HINSTANCE)), _
NULL)
END IF
SetWindowTextW hMdi, "MDI Child Window " & STR(nIdx)
END IF
EXIT FUNCTION
' // Tile horizontally
CASE IDM_TILEH
IF hwndClient THEN SendMessageW hwndClient, WM_MDITILE, MDITILE_HORIZONTAL, 0
EXIT FUNCTION
' // Tile vertically
CASE IDM_TILEV
IF hwndClient THEN SendMessageW hwndClient, WM_MDITILE, MDITILE_VERTICAL, 0
EXIT FUNCTION
' // Cascade windows
CASE IDM_CASCADE
IF hwndClient THEN SendMessageW hwndClient, WM_MDICASCADE, 0, 0
EXIT FUNCTION
' // Arrange icons
CASE IDM_ARRANGE
IF hwndClient THEN SendMessageW hwndClient, WM_MDIICONARRANGE, 0, 0
EXIT FUNCTION
CASE IDM_CLOSE
' // Close the active window
IF hwndClient THEN
hwndActive = CAST(HANDLE, SendMessageW(hwndClient, WM_MDIGETACTIVE, 0, 0))
IF SendMessageW(hwndActive, WM_QUERYENDSESSION, 0, 0) THEN
SendMessageW hwndClient, WM_MDIDESTROY, CAST(LPARAM, hwndActive), 0
END IF
END IF
EXIT FUNCTION
CASE IDM_CLOSEALL
' // Close all the MDI child windows
IF hwndClient THEN
EnumChildWindows hwndClient, @CWindow_CloseEnumProc, 0
END IF
EXIT FUNCTION
' // Exit the application
CASE IDM_EXIT
SendMessageW hwnd, WM_CLOSE, 0, 0
EXIT FUNCTION
END SELECT
' // Pass unprocessed messages to the active MDI child and then to DefFrameProc()
hwndActive = CAST(HWND, SendMessageW(hwndClient, WM_MDIGETACTIVE, 0, 0))
IF IsWindow(hwndActive) THEN SendMessageW hwndActive, WM_COMMAND, wParam, lParam
CASE WM_NOTIFY
ptnmhdr = CAST(NMHDR PTR, lParam)
' SELECT CASE ptnmhdr->idFrom
' ' ...
' ' ...
' END SELECT
' // Pass unprocessed messages to the active MDI child and then to DefFrameProc()
IF hwndClient THEN
hwndActive = CAST(HWND, SendMessageW(hwndClient, WM_MDIGETACTIVE, 0, 0))
IF IsWindow(hwndActive) THEN SendMessageW hwndActive, WM_NOTIFY, wParam, lParam
END IF
CASE WM_SIZE
' // If the window isn't minimized, resize it
IF wParam <> SIZE_MINIMIZED THEN
IF hwndClient <> NULL AND pWindow <> NULL THEN
pWindow->MoveWindow hwndClient, 0, 0, pWindow->ClientWidth + 2, pWindow->ClientHeight + 2, TRUE
END IF
END IF
' // Note: This message is not passed to DefFrameProc when space
' // is being reserved in the client area of the MDI frame
' // or controls on the MDI frame are resizeable.
EXIT FUNCTION
CASE WM_CLOSE
IF hwndClient THEN
' // Attempt to close all MDI child windows
EnumChildWindows hwndClient, @CWindow_CloseEnumProc, 0
' // If child windows are still open abort closing the application
IF GetWindow(hwndClient, GW_CHILD) THEN EXIT FUNCTION
END IF
CASE WM_QUERYENDSESSION
IF hwndClient THEN
' // Attempt to close all MDI child windows
EnumChildWindows hwndClient, @CWindow_CloseEnumProc, 0
' // If child windows are still open abort closing the application
IF GetWindow(hwndClient, GW_CHILD) THEN EXIT FUNCTION
END IF
CASE WM_DESTROY
PostQuitMessage(0)
EXIT FUNCTION
END SELECT
IF hwndClient THEN
' // The DefFrameProc function provides default processing for any window
' // messages that the window procedure of a multiple-document interface (MDI)
' // frame window does not process. All window messages that are not explicitly
' // processed by the window procedure must be passed to the DefFrameProc
' // function, not the DefWindowProc function.
FUNCTION = DefFrameProcW(hwnd, hwndClient, uMsg, wParam, lParam)
ELSE
' // The DefWindowProc function calls the default window procedure to provide
' // default processing for any window messages that an application does not process.
' // This function ensures that every message is processed. DefWindowProc
' // is called with the same parameters received by the window procedure.
FUNCTION = DefWindowProcW(hwnd, uMsg, wParam, lParam)
END IF
END FUNCTION
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // Set process DPI aware
AfxSetProcessDPIAware
DIM pWindow AS CWindow
pWindow.Create(NULL, "MDI with CWindow", @WndProc)
' // Change the window style to avoid flicker
pWindow.ClassStyle = CS_DBLCLKS
' // Set the client size
pWindow.SetClientSize 650, 400
' // Center the window
pWindow.Center
' // Create a menu
DIM hMenu AS HMENU
hMenu = BuildMenu
SetMenu pWindow.hWindow, hMenu
'// Create a MDI client child window
DIM hwindowMenu AS HMENU
hwindowMenu = GetSubMenu(hMenu, 2)
pWindow.CreateMDIWindow(101, 0, 0, 0, 0, 0, 0, hwindowMenu, @MDIWindowProc)
FUNCTION = pWindow.DoEvents(nCmdShow)
END FUNCTION
' ========================================================================================
Works perfectly. Although I never use MDI windows anymore in my applications I am sure that some people still do and this will be very useful for them.
I only have used MDI once, for my CSED editor. However, if you don't add support for it, somebody will say: But it has not support for MDI like <put here the name of some multi megabyte library>... :)
Some people asks for MDI, but when they see that it is hard stuff, they change of idea and use tabs. And if you're a DDTer then you have no choice, because DDT does not support MDI.
As probably nobody will use it, I have wrapped all the code with ifdefs to avoid bloat.
Anyway, this template explains with detail all the needed steps. I like to write commented templates because they are ready to use from a click of my editor. Great for quick examples or tests.