• Welcome to PlanetSquires Forums.
 

CWindow: MDI Framework

Started by José Roca, September 10, 2015, 08:26:18 AM

Previous topic - Next topic

José Roca

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
' ========================================================================================


Paul Squires

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.
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

José Roca

#2
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.