Уроки Iczelion'а

       

Создание MDI-приложения


Этот туториал расскажет, как создать MDI-приложение. Это не так сложно.

Скачайте пример.

Теория:

Мультидокументный нтерфейс - это спецификация для приложений, которые обрабатывают несколько документов в одно и то же время. Вы знакомы с Noteрad'оам: это пример однодокументного интерфейса (SDI). Noteрad может обрабатывать только один документ за раз. Если вы хотите открыть другой документ, вам нужно закрыть предыдущий. Как вы можете себе представить, это довольно неудобно. Сравните его с Microsoft Word: тот может держать открытыми различные документы в одно и то же время и позволяет пользователю выбирать, какой документ использовать.

У MDI-приложений есть несколько характеристик, присущих только им. Я перечислю некоторые из них:

  • Внутри основного окна может быть несколько дочерних окон в пределах клиентской области.
  • Когда вы сворачиваете окно, он сворачивается к нижнему левому углу клиентской области основного окна.
  • Когда вы разворачиваете окно, его заголовок сливается с заголовком главного окна.
  • Вы можете закрыть дочернее окно, нажав Ctrl+F4 и переключатся между дочерними окнами, нажав на Ctrl+Tab.

Главное окно, которое содержит дочерние окно называется фреймовым окном. Его клиентская область - это место, где находятся дочерние окна, поэтому оно и называется фреймовым (на английском 'frame' означает "рамка, рама"). Его работа чуть более сложна, чем задачи обычного окна, так как оно обеспечивает работу MDI.

Чтобы контролировать дочерние окна в клиентской области, вам нужно специальное окно, которое называется клиентским окном. Вы можете считать это клиентское окно прозрачным окном, покрывающим всю клиенсткую область фреймового окна.

Фреймовое окно | Клиентское окно | | | | | | MDI Child 1 MDI Child 2 MDI Child 3 MDI Child 4 MDI Child n

Рисунок 1. Иерархия MDI-приложения

Создание фреймового окна



Теперь мы переключим наше внимание на детали. Прежде всего вам нужно создать фремовое окно. Оно создается примерно таким же образом, как и обычное окно: с помощью вызова CreateWindowEx. Есть два основных отличия от создания обычного окна.


Первое различие состоит в том, что вы ДОЛЖHЫ вызывать DefFramProc вместо DefWindowProc для обработки Windows-сообщение вашему окну, которые вы не хотите обрабатывать самостоятельно. Это единственный путь заставить Windows делать за вас гразную работу по управлению MDI-приложение. Если вы забудете использовать DefFramProc, ваше приложение не будет иметь MDI-свойств. DefFrameProc имеет следующий синтакс:
DefFrameProc proc hwndFrame:DWORD, hwndClient:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
Если вы сравните DefFramProc с DefWindowProc, вы заметите, что разница между ними состоит в том, что у DefFrameProc пять параметров, в то время как у DefWindowProc только четыре. Дополнительный параметр - это хэндл клиенсткого окна. Это хэндл необходим для того, чтобы Windows могла посылать MDI-сообщения клиенсткому окну.
Второе различие заключается в том, что вы должны вызывать TranslateMDISysAccel в цикле обработки сообщений вашего фреймового окна. Это необходим, если вы хотите, что Windows обрабатывала нажатия на комбинации клавиш, связанных с MDI, такие как Ctrl+F4, Ctrl+Tab. У этой функции следующий прототип:
TranslateMDISysAccel proc hwndClient:DWORD, lpMsg:DWORD
Первый параметр - это хэндл клиентского окна. Для вас не должно быть сюрпризом, что клиентское окно будет родителем окном для все дочерних MDI-окон. Второй параметр - это адрес MSG-структуры, котоурю вы заполните с помощью функции getMessage. Идея состоит в том, чтобы передать MSG-структуру клиентскому окну, что оно могло проверить, содержит ли эта структура искомые комбинации клавиш. Если это так, она обрабатывает само сообщение и возвращает ненулевое значение, или, в противном случае, FALSE.
Этапы создания фреймовое окно могут быть кратко просуммированы:

  • Заполняем структуру WNDCLASSEX как обычно.

  • Регистрируем класс фреймового окна, вызвая RegisterClassEx.

  • Создаем фреймовое окно с помощью CreateWindowEx.

  • Внутри цикла обработки сообщений вызываем TranslateMDISysAccel.

  • Внутри процедуры окна передаем необрабатанные сообщения DefFrameProc вместо DefWindowProc.



Создание клиентского окна
Теперь, когда у нас есть фреймовое окно, мы можем создать клиентское окно. Класс клиентского окна перерегистрирован Windows. Имя этого класса - "MDICLIENT". Вам также нужно передать адрес структуры CLIENTCREATESTRUCT функции CreateWindowEx. Эта структура имеет следующее определение:
CLIENTCREATESTRUCT struct hWindowMenu dd ? idFirstChild dd ? CLIENTCREATESTRUCT ends
hWindowMenu - это хэндл подменю, к которому Windows присоединит список имен дочерних MDI-окон. Здесь требуется некоторое пояснение. Если вы когда-нибудь использовали раньше MDI-приложение вроде Microsoft Word, вы могли заметить, что у него есть подменю под названием "window", которое при активации отображает различные пункты меню, связанные с управлением дочерними окнами, а также список открытых дочерних окон. Этот список создается самими Windows: вам не нужно прилагать специальных усилий. Всего лишь передайте хэндл подменю, к которому должен быть присоединен список, а Windows возьмет на себя все остальное. Обратите внимание, что подменю может быть любым: не обязательно тем, которое названно "window". Если вам не нужен список окон, просто передайте NULL в hWindowMenu. Получить хэндл подменю можно с помощью GetSubMenu.
idFirstChild - ID первого дочернего MDI-окна. Windows увеличивает ID на 1 для каждого нового MDI-окна, которое создает приложение. Например, если вы передает 100 через это поле, первое MDI-окно будет иметь ID 100, второе - 101 и так далее. Это ID посылается фреймовому окну через WM_COMMAND, когда дочернее MDI-окно выбрано из списка окон. Обычно вы будете передавать эти "необрабатываемые" сообщения процедуре DefFrameProc. Я использую слово "необрабатываемые", потому что пункты меню списка окон не создаются вашим приложением, поэтому ваше приложение не знает их ID и не имеет обработчика для них. Поэтому для фреймового окна есть специальное правило: если у вас есть список окон, вы должны модифицировать ваш обработчик WM_COMMAND так:


.elseif uMsg==WM_COMMAND
.if lParam==0 mov eax,wParam .if ax==IDM_CASCADE .....
.elseif ax==IDM_TILEVERT ..... . else invoke DefFrameProc, hwndFrame, hwndClient, uMsg,wParam, ret .endif
Обычно вам следует игнорировать сообщения о необрабатываемых событиях, но в случае с MDI, если вы просто проигнорируете их, то когда пользователь кликнет на имени дочернего MDI-окна, это окно не станет активным. Вам следует передавать управление DefFrameProc, чтобы они были правильно обработаны.
Я хочу предупредить вас относительно возможного значения idFirstChild: вам не следует использовать 0. Ваш список окон будет себя вести неправильно, то есть напротив пункта меню, обозначающего активное MDI-окно, не будет галочки. Лучше выберите какое-нибудь безопасное значение вроде 100 или выше.
Заполнив структуру CLIENTCREATESTRUCT, вы можете создать клиентское окно, вызвав CreateWindowEx, указав предопределенный класс "MDICLIENT" и передав адрес структуры CLIENTCREATESTRUCT через lParam. Вы должны также указать хэндл на фреймовое окно в параметре hWndParent, чтобы Windows знала об отношениях родитель-ребенок между фреймовым окно и клиентским окном. Вам следует использовать следующие стили окна: WS_CHILD, WS_VISIBLE и WS_CLIPCHILDREN. Если вы забудете указать стиль WS_VISIBLE, то не увидите дочерних MDI-окон, даже если они будут созданы успешно.
Этапы создания клиентского окна следующие:
  • Получить хэндл на подменю, к которому вы хотите присоединить список окон.

  • Поместите значение хэндла меню и значения, которое вы хотите использовать как ID первого дочернего MDI-окна в структуру CLIENCREATESTRUCT.

  • Вызовите CreateWindosEx, передав имя класса "MDICLIENT" и адрес структуры CLIENTCREATESTRUCT, которую вы только что заполнили, через lParam.

  • Создание дочернего MDI-окна.
    Теперь у вас есть и фреймовое и клиентское окно. Теперь все готово для создания дочернего MDI-окна. Есть два пути сделать это.

    • Вы можете послать сообщение WM_MDICREATE клиентскому окн, передав тому адрес структуры типа MDICREATESTRUCT через wParam. Это простейший и наиболее часто используемый способ создания дочерних MDI-окон.



    .data? mdicreate MDICREATESTRUCT <> .... .code ..... [ fill the members of mdicreate] [ заполняем поля структуры mdicreate ] ...... invoke SendMessage, hwndClient, WM_MDICREATE,addr mdicreate,0
    Функция SendMessage возвратит хэндл только что созданного дочернего MDI-оанк, если все пройдет успешно. Вам не нужно сохранять хэндл. Вы можете получить его каким-либо другим образом, если хотите. У структуры MDICREATESTRUCT следующее определение:
    MDICREATESTRUCT STRUCT szClass DWORD ? szTitle DWORD ? hOwner DWORD ? x DWORD ? y DWORD ? lx DWORD ? ly DWORD ? style DWORD ? lParam DWORD ? MDICREATESTRUCT ENDS
  • szClass - адрес класса окна, который вы хотите использовать в качестве шаблона для дочернего MDI-окна.

  • szTitle - адрес строки, которая должна появиться в заголовке дочернего окна.

  • hOwner - хэндл приложения.

  • x, y, lx, ly - верхняя левая координата, ширина и высота дочернего окна.

  • style - стиль дочернего окна. Если вы создали клиентское окно со стилем MDIS_ALLCHILDSTYLES, то вы можете использовать все стили.

  • lParam - определяемое программистом 32-х битное значение. Используется для передачи значение между MDI-окнами. Если у вас нет подобной нужны, просто поставьте их в NULL.

  • Вы можете вызвать CreateMDIWindow. Эта функция имеет следующий синтаксис:

  • CreateMDIWindow proto lpClassName:DWORD lpWindowName:DWORD dwStyle:DWORD x:DWORD y:DWORD nWidth:DWORD nHeight:DWORD hWndParent:DWORD hInstance:DWORD lParam:DWORD
    Если вы внимательно посмотрите на параметры, вы увидите, что они идентичны параметрам структуры MDICREATESTRUCT не считая hWndParent. Очевидно, что точно такое количество параметров вы передавали вместе с сообщением WM_MDICREATE. У структуры MDICREATESTRUCT нет поля hWndParent, потому что вы все равно должны передавать структуру клиентскому окну с помощью функции SendMessage.
    Сейчас вы можете спросить: какой метод я должен выбрать? Какая разница между этими двумя методами? Вот ответ:
    Метод WM_MDCREATE создает дочернее MDI-окно в том же треде, что и вызывающий код. Это означает, что если у приложения есть только одна ветвь, все дочерние MDI-окна будут выполняться в контексте основной ветви. Это не слишком большая проблема, если одно или более из дочерних MDI-окон не выполняет какие-либо продолжительные операции, что может стать проблемой. Подумайте об этом, иначе в какой-то момент все ваше приложение внезапно зависнет, пока операция не будет выполнена.


    Эта проблема как раз то, что призвана решить функция CreateMDIWindow. Она создает отдельный тред для каждого из дочерних MDI-окон, поэтому если одно из них занято, оно не приводит к зависанию всего приложения.
    Необходимо сказать несколько слов относительно оконной процедуры дочернего MDI-окна. Как и в случае с фреймовым окном, вы не должны вызывать DefWindowProc, чтобы обработать необрабатываемые вашим приложением сообщением. Вместо этого вы должны использовать DefMDIChildProc. У этой функции точно такие же параметры, как и у DefWindowProc.
    Кроме WM_MDICREATE, есть еще несколько сообщений, относящихся к MDI-окнам.
    Я приведу их описание:

    • WM_MDIACTIVATE - это сообщение может быть послано приложением клиентскому окну, чтобы последнее активировало выбранное дочернее MDI-окно. Когда клиентское окно получает сообщение, оно активирует выбранное дочернее MDI-окно, а также посылает это же сообщение окну, которое было активированы или дезактивировано. Таким образом, данное сообщение имеет двойное назначение: с его помощью приложение может активировать выбранное дочернее окно, а также оно может быть использовано дочерним MDI-приложением для определения того, активировано оно или нет. Например, если каждое дочернее MDI-окно имеет различно меню, оно может использовать эту возможность для изменения меню фреймового окна при активации/дезактивации.

    • WM_MDICASCADE, WM_MDITILE, WM_MDICONARRANGE - эти сообщения отвечаю за расположение дочерних MDI-окон. Hапример, если вы хотите, чтобы дочерние MDI-окна pасположились каскадом, пошлите сообщение WM_MDICASCADE клиентскому окну.

    • WM_MDIDESTROY - пошлите это сообщение клиентскому окну, если хотите уничтожить дочернее MDI_окно. Вам следует использовать это сообщение вместо DestroyWindow, потому что если дочернее MDI-приложение максимизировано, это сообщение восстановить заголовок фреймового окна, что не будет сделано в случае применения функции DestroyWindow.

    • WM_MDIGETACTIVE - используйте это сообщение, чтобы получить хэндл активного в настоящий момент дочернего MDI-окна.



    • WM_MDIMAXIMIZE, WM_MDIRESTORE - используйте WM_MDIMAXIZE для разворачивания дочернего MDI-окна и WM_MDIRESTORE для его восстановления. Всегда используйте эти сообщения для данных операций. Если вы используете ShowWindow с SW_MAXIMIZE, дочернее MDI-окно будет развернуто, но у вас появятся проблемы, когда вы захотите восстановить его до прежнего размера. Вы можете минимизировать окно с помощью ShowWindow без всяких проблем.

    • WM_MDINEXT - посылайте это сообщение клиентскому окну, чтобы активировать следующее или предыдущее дочернее MDI-окно, согласно значению wParam и lParam.

    • WM_MDIREFRESHMENU - это сообщение посылается клиентскому окну, чтобы обновить меню фреймового окна. Обратите внимание, что вы должны вызвать DrawMenuBar для обновления меню баp после отсылки данного сообщения.

    • WM_MDISETMENU - посылайте это сообщение клиентскому окну, что полностью заменить меню фреймового окна или только подменю окон. Вы должны использовать данное сообщение вместо SetMenu. После того, как вы отослали данное сообщение, вы должны вызвать DrawMenuBar. Обычно вы будете использовать это сообщение, когда у активного дочернего MDI-окна есть свое меню и вы хотите, чтобы оно заменяло меню фреймового окна, пока это дочернее MDI-окно активно.

    Я сделаю небольшое обозрение создания MDI-приложения еще раз:

    • Регистрируем классы окна, фреймового класса и дочернего MDI-окна.

    • Создаем фреймовое окно с помощью CreateWindowEx.

    • Внутри цикла обработки сообщений вызываем TranslateMDISysAccel, чтобы обработать "горячие клавиши", относящиеся к MDI.

    • Внутри оконной процедуры фреймового окна вызываем DefFramProc, чтобы обрабатывать все сообщения, необрабатываемые приложением.

    • Создаем клиентское окно, вызвав CreateWindowEx, которой передаем имя предопределенного класса окна, "MDICLIENT", передавая адрес структуры CLIENTCREATESTRUCT через lParam. Обычно вы будете создавать клиентское окно внутри обработчика сообщения WM_CREATE фреймового окна.

    • Вы можете создать дочернее MDI-окно, послав клиентскому окну сообщение WM_MDICREATE или вызвав функцию CreateMDIWindow.



    • Внутри оконной процедуры дочернего MDI-ОКHА, передаем все необработанные сообщения DefMDIChildProc.

    • Используем MDi- версии сообщений, если таковые существуют. Hапример, вместо WM_MDIDESTROY вместо DestroyWindow.

    ПРИМЕР
    .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
    .const IDR_MAINMENU equ 101 IDR_CHILDMENU equ 102 IDM_EXIT equ 40001 IDM_TILEHORZ equ 40002 IDM_TILEVERT equ 40003 IDM_CASCADE equ 40004 IDM_NEW equ 40005 IDM_CLOSE equ 40006
    .data ClassName db "MDIASMClass",0 MDIClientName db "MDICLIENT",0 MDIChildClassName db "Win32asmMDIChild",0 MDIChildTitle db "MDI Child",0 AppName db "Win32asm MDI Demo",0 ClosePromptMessage db "Are you sure you want to close this window?",0
    .data? hInstance dd ? hMainMenu dd ? hwndClient dd ? hChildMenu dd ? mdicreate MDICREATESTRUCT <> hwndFrame dd ?
    .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax
    WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG ;============================================= ; Register the frame window class ;============================================= mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc,OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE mov wc.lpszMenuName,IDR_MAINMENU mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc ;================================================ ; Register the MDI child window class ;================================================ mov wc.lpfnWndProc,offset ChildProc mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszClassName,offset MDIChildClassName invoke RegisterClassEx,addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,\ hInst,NULL mov hwndFrame,eax invoke LoadMenu,hInstance, IDR_CHILDMENU mov hChildMenu,eax invoke ShowWindow,hwndFrame,SW_SHOWNORMAL invoke UpdateWindow, hwndFrame .while TRUE invoke GetMessage,ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMDISysAccel,hwndClient,addr msg .if !eax invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endif .endw invoke DestroyMenu, hChildMenu mov eax,msg.wParam ret WinMain endp


    WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL ClientStruct:CLIENTCREATESTRUCT .if uMsg==WM_CREATE invoke GetMenu,hWnd mov hMainMenu,eax invoke GetSubMenu,hMainMenu,1 mov ClientStruct.hWindowMenu,eax mov ClientStruct.idFirstChild,100 INVOKE CreateWindowEx,NULL,ADDR MDIClientName,NULL,\ WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWn hInstance,addr ClientStruct mov hwndClient,eax ;======================================= ; Initialize the MDICREATESTRUCT ;======================================= mov mdicreate.szClass,offset MDIChildClassName mov mdicreate.szTitle, offset MDIChildTitle push hInstance pop mdicreate.hOwner mov mdicreate.x,CW_USEDEFAULT mov mdicreate.y,CW_USEDEFAULT mov mdicreate.lx,CW_USEDEFAULT mov mdicreate.ly,CW_USEDEFAULT .elseif uMsg==WM_COMMAND .if lParam==0 mov eax,wParam .if ax==IDM_EXIT invoke SendMessage,hWnd,WM_CLOSE,0,0 .elseif ax==IDM_TILEHORZ invoke SendMessage,hwndClient,WM_MDITILE,MDIT .elseif ax==IDM_TILEVERT invoke SendMessage,hwndClient,WM_MDITILE,MDIT .elseif ax==IDM_CASCADE invoke SendMessage,hwndClient,WM_MDICASCADE,M .elseif ax==IDM_NEW invoke SendMessage,hwndClient,WM_MDICREATE,0, .elseif ax==IDM_CLOSE invoke SendMessage,hwndClient,WM_MDIGETACTIVE invoke SendMessage,eax,WM_CLOSE,0,0 .else invoke DefFrameProc,hWnd,hwndClient,uMsg,wPar ret .endif .endif .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp
    ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_MDIACTIVATE mov eax,lParam .if eax==hChild invoke GetSubMenu,hChildMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMen .else invoke GetSubMenu,hMainMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu .endif invoke DrawMenuBar,hwndFrame .elseif uMsg==WM_CLOSE invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName .if eax==IDYES invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0 .endif .else invoke DefMDIChildProc,hChild,uMsg,wParam,lParam ret .endif xor eax,eax ret ChildProc endp end start


    АНАЛИЗ
    Первое, что должна сделать программа - это зарегистрировать классы фреймового и дочернего MDI-окна. После этого она вызывает функцию CreateWindowEx, чтобы создать фреймовое окно. Внутри обработчика WM_CREATE фреймового окна мы создаем клиентское окно:
    LOCAL ClientStruct:CLIENTCREATESTRUCT .if uMsg==WM_CREATE invoke GetMenu,hWnd mov hMainMenu,eax invoke GetSubMenu,hMainMenu,1 mov ClientStruct.hWindowMenu,eax mov ClientStruct.idFirstChild,100 invoke CreateWindowEx,NULL,ADDR MDIClientName,NULL,\ WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAU CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\ hInstance,addr ClientStruct mov hwndClient,eax
    Здесь мы вызываем GetMenu, чтобы полуть хэндл меню фреймового окна, который будем использовать в GetSubMenu. Обратите внимание, что мы передаем 1 функции GetSubMenu, потому что подменю, к которому мы будем присоединять список окон, является вторым подменю. Затем мы заполняем параметры структуры CLIENTCREATESTRUCT.
    Затем мы инициализируем структуру MDCLIENTSTRUCT. Обратите внимание, что мы не обязаны делать это здесь. Просто это удобнее осуществлять в обработчике WM_CREATE.
    mov mdicreate.szClass,offset MDIChildClassName mov mdicreate.szTitle,offset MDIChildTitle push hInstance pop mdicreate.hOwner mov mdicreate.x,CW_USEDEFAULT mov mdicreate.y,CW_USEDEFAULT mov mdicreate.lx,CW_USEDEFAULT mov mdicreate.ly,CW_USEDEFAULT
    После того, как фреймовое окно создано (так же как клиентское окно), мы вызывает LoadMenu, чтобы загрузить меню дочернего окна из ресурса. Hам нужно получить хэндл этого меню, чтобы мы могли заменить меню фреймового окна, когда дочернее MDI-окно становится активным. Hе забудьте вызвать DestroyMenu, прежде чем приложение завершит работу. Обычно Windows сама освобождает память, занятую меню, но в данном случае этого не произойдет, так как меню дочернего окна не ассоциировано ни с каким окном, поэтому оно все еще будет занимать ценную память, хотя приложение уже прекратило свое выполнение.
    invoke LoadMenu,hInstance, IDR_CHILDMENU mov hChildMenu,eax ........ invoke DestroyMenu, hChildMenu


    Внутри цикла обработки сообщений, мы вызываем TranslateMDISysAccel.
    .while TRUE invoke GetMessage,ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMDISysAccel,hwndClient,addr msg .if !eax invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endif .endw
    Если TranslateMDISysAccel возвращает ненулевое значение, это означает, что собщение уже было обработано Windows, поэтому вам не нужно делать что-либо с ним. Если был возвращен 0, сообщение не относится к MDI и поэтому должно обрабатываться как обычно.
    WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM ..... .else invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp
    Обратите внимание, что внутри оконной процедуры фреймового окна мы вызываем DefFrameProc для обработки сообщений, которые не представляют для нас интереса.
    Основной часть процедуры окна является обработчик сообщения WM_COMMAND. Когда пользователь выбирает в меню пункт "New", мы создает новое дочернее MDI-окно.
    .elseif ax==IDM_NEW invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate
    В нашем примере мы создаем дочернее MDI-окно, посылая WM_MDIREATE клиентскому окну, передавая адрес структуры MDICREATESTRUCT через lParam.
    ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_MDIACTIVATE mov eax,lParam .if eax==hChild invoke GetSubMenu,hChildMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMen .else invoke GetSubMenu,hMainMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu .endif invoke DrawMenuBar,hwndFrame
    Когда создано дочернее MDI-окно, оно отслеживает сообщение WM_MDIACTIVATE, чтобы определить, является ли оно в данный момент активным. Оно делает это сравнивая значение lParam, которое содержит хэндл активного дочернего окна со своим собственным хэндлом.
    Если они совпадают, значит оно является активным и следующим шагом будет замена меню фреймового окна на свое собственное. Так как изначально меню будет заменено, вам надо будет указать Windows снова в каком подменю должен появиться список окон. Поэтому мы должны снова вызвать функцию GetSubMenu, чтобы получить хэндл подменю. Мы посылаем сообщение WM_MDISETMENU клиентскому окну, достигая, таким образом, желаемого результата. Параметр wParam сообщения WM_MDISETMENU содержит хэндл меню, которое заменит оригинальное. lParam содержит хэндл подменю, к которому будет присоединен список окон. Сразу после отсылки сообщения WM_MDISETMENU, мы вызываем DrawMenuBar, чтобы обновить меню, иначе произойдет большая путаница.


    .else invoke DefMDIChildProc,hChild,uMsg,wParam,lParam ret .endif
    Внутри оконной процедуры дочернего MDI- окна вы должны передать все необработанные сообщения функции DefMDIChildProc.
    .elseif ax==IDM_TILEHORZ invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0 .elseif ax==IDM_TILEVERT invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0 .elseif ax==IDM_CASCADE invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISAB
    Когда пользователь выбирает один из пунктов меню в подменю окон, мы посылаем соответствующее сообщение клиентскому окну. Если пользователь выбирает один из методов расположения окон, мы посылаем WM_MDITILE или WM_CASCADE.
    .elseif ax==IDM_CLOSE invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0 invoke SendMessage,eax,WM_CLOSE,0,0
    Если пользователь выбирает пункт меню "Close", мы должны получить хэндл текущего активного MDI-окна.
    .elseif uMsg==WM_CLOSE invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName .if eax==IDYES invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0 .endif
    Когда процедура дочернего MDI-окна получает сообщение WM_CLOSE, его обработчик отображает окно, которое спрашивает пользователя, действительно ли он хочет закрыть окно. Если ответ - "Да", то мы посылаем клиентскому окну сообщение WM_MDIDESTROY, которое закрывает дочернее MDI-окно и восстанавливает заголовок фреймового окна.
    [C] Iczelion, пер. Aquila.

    Содержание раздела