![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr Jerzy Wałaszek I LO w Tarnowie
|
Ta lekcja uczy tworzenia aplikacji MDI (Multi Document Interface - Interfejs Wielo Dokumentowy). W zasadzie nie jest to zbyt trudne.

Załaduj {ten przykład}
Interfejs Wielo Dokumentowy (MDI) jest typem aplikacji, która obsługuje wiele dokumentów w tym samym czasie. Na pewno znasz Notatnik: jest to przykład Interfejsu Jedno Dokumentowego (SDI - Single Document Interface). Notatnik może jednocześnie obsługiwać tylko jeden dokument. Jeśli chcesz otworzyć następny, to najpierw musisz zamknąć poprzedni. Oczywiście nie jest to zbyt wygodne. Przeciwieństwem jest procesor tekstu Microsoft Word, który jednocześnie potrafi otworzyć dowolną liczbę dokumentów i pozwala użytkownikowi wybrać jeden z nich do pracy. Microsoft Word jest przykładem Interfejsu Wielo Dokumentowego.
Aplikacja MDI posiada kilka charakterystyk, które ją wyróżniają. Przedstawię niektóre z nich:



Główne okno zawierające okna potomne nazywane jest oknem ramowym (frame window). To na jego obszarze roboczym żyją okna potomne, stąd nazwa "rama". Ma ono nieco więcej do roboty od zwykłego okna, ponieważ musi obsłużyć współdziałanie okien potomnych.
Aby kontrolować dowolną liczbę okien potomnych (child windows) na swoim obszarze roboczym, potrzebujesz specjalnego okna zwanego oknem klienckim (client window). Możesz je potraktować jak przeźroczyste okno pokrywające całość obszaru roboczego okna ramowego. To okno klienckie jest prawdziwym oknem nadrzędnym dla wszystkich okien potomnych MDI.

Rysunek 1. Hierarchia aplikacji MDI
Teraz zajmiemy się szczegółami. Na początek utworzymy okno ramowe. Tworzy się je w identyczny sposób jak każde okno przez wywołanie funkcji CreateWindowEx. Istnieją jednak dwie główne różnice w stosunku do zwykłych okien.
Pierwszą jest to, iż MUSISZ wywołać w swojej procedurze okna funkcję DefFrameProc zamiast DefWindowProc do obsługi wiadomości systemu Windows, których sam nie obsługujesz. W ten sposób zwalasz całą brudną robotę zarządzania aplikacją MDI na system Windows. Jeśli zapomnisz sobie o wywołaniu DefFrameProc, twoja aplikacja nie będzie posiadała cech aplikacji MDI. Funkcja DefFrameProc posiada następującą składnię:
DefFrameProc PROC hwndFrame: DWORD,
hwndClient: DWORD,
uMsg: DWORD,
wParam: DWORD,
lParam: DWORD
Jeśli porównasz funkcję DefFrameProc z DefWindowProc, zauważysz, iż jedyną różnicą pomiędzy nimi jest to, iż funkcja DefFrameProc posiada 5 parametrów, natomiast DefWindowProc ma tylko 4. Dodatkowy parametr jest uchwytem okna klienckiego. Ten uchwyt jest konieczny, aby system Windows mógł wysyłać wiadomości związane z MDI do okna klienckiego.
Drugą różnicą jest to, iż w pętli wiadomości okna ramowego musisz wywołać funkcję TranslateMDISysAccel. Jest to niezbędne, jeśli chcesz, aby system Windows obsługiwał dla ciebie skróty klawiaturowe związane z MDI, takie jak Ctrl+F4, Ctrl+Tab. Posiada ona następującą składnię:
TranslateMDISysAccel PROTO hwndClient: DWORD,
lpMsg: DWORD
Pierwszy parametr jest uchwytem okna klienckiego. Nie powinno cię to zaskoczyć, ponieważ jest ono oknem nadrzędnym dla wszystkich okien potomnych MDI. Drugi parametr jest adresem struktury MSG, którą wypełniasz wywołując funkcję GetMessage. Celem jest przesłanie tej struktury do okna klienckiego, aby mogło ono sprawdzić, czy struktura MSG nie zawiera wiadomości o naciśnięciu klawiszy związanych z obsługą MDI. Jeśli tak, obsłuży ono samodzielnie te zdarzenia i zwróci wartość różną od zera, w przeciwnym razie zwróci FALSE.
Podsumujmy następująco kroki niezbędne do utworzenia okna ramowego:
Mając już okno ramowe możemy przejść do utworzenia okna klienckiego. Klasa okna klienckiego jest już zarejestrowana w systemie Windows. Posiada ona nazwę "MDICLIENT". Do funkcji CreateWindowEx musisz również przekazać adres struktury CLIENTCREATESTRUCT. Struktura ta posiada następującą definicję:
CLIENTCREATESTRUCT STRUCT
hWindowMenu DD ?
idFirstChild DD ?
CLIENTCREATESTRUCT ENDS .ELSEIF uMsg==WM_COMMAND
.IF lParam==0 ;ta wiadomość pochodzi od menu
mov eax, wParam
.IF ax==IDM_CASCADE
...
.ELSEIF ax==IDM_TILEVERT
...
.ELSE
INVOKE DefFrameProc, hwndFrame,
hwndClient, uMsg,wParam, lParam
ret
.ENDIF
.ENDIF
Zwykle ignorujemy wiadomości z nieobsługiwanych przypadków. Lecz dla MDI, jeśli zignorujesz je, gdy użytkownik klika nazwę okna potomnego MDI na liście okien, to wybrane okno nie stanie się aktywne. Musisz przekazać je do DefFrameProc, aby zostały poprawnie obsłużone.
Ostrożnie z wartością pola idFirstChild: nie należy używać wartości 0. Lista okien w takim przypadku zacznie się niepoprawnie zachowywać, tj. znaczek wyboru nie pojawi się przed nazwą pierwszego okna potomnego MDI, nawet jeśli będzie ono aktywne. Wybierz jakąś bezpieczną wartość, taką jak 100 lub więcej.
Wypełniwszy pola struktury CLIENTCREATESTRUCT, możesz utworzyć okno klienckie przez wywołanie CreateWindowEx z predefiniowaną nazwą klasy "MDICLIENT" i przekazanie w lParam adresu struktury CLIENTCREATESTRUCT. W parametrze hWndParent musisz również podać uchwyt do okna ramowego, aby system Windows znał relacje pomiędzy oknem ramowym a oknem klienckim. Powinieneś użyć następujących stylów: WS_CHILD, WS_VISIBLE u WS_CLIPCHILDREN. Jeśli zapomnisz o stylu WS_VISIBLE, nie zobaczysz okien potomnych MDI, nawet jeśli zostaną utworzone z powodzeniem.
Kroki tworzenia okna klienckiego są następujące:
Teraz masz już oba okna, ramowe i klienckie. Zostało przygotowane miejsce na utworzenie okna potomnego MDI. Można to zrobić na dwa sposoby.
.DATA?
mdicreate MDICREATESTRUCT <>
...
.CODE
...
[wypełnij pola struktury mdicreate]
...
INVOKE SendMessage, hwndClient, WM_MDICREATE, ADDR mdicreate, 0Jeśli operacja się powiedzie, to funkcja SendMessage zwróci uchwyt do nowo utworzonego okna potomnego MDI. Jednakże nie musisz go zachowywać. Można ten uchwyt uzyskać innymi środkami, jeśli stanie się potrzebny. Struktura MDICREATESTRUCT posiada następującą definicję:
MDICREATESTRUCT STRUCT
szClass DWORD ?
szTitle DWORD ?
hOwner DWORD ?
x DWORD ?
y DWORD ?
lx DWORD ?
ly DWORD ?
style DWORD ?
lParam DWORD ?
MDICREATESTRUCT ENDSCreateMDIWindow PROTO lpClassName: DWORD
lpWindowName: DWORD
dwStyle: DWORD
x: DWORD
y: DWORD
nWidth: DWORD
nHeight: DWORD
hWndParent: DWORD
hInstance: DWORD
lParam: DWORD
Jeśli dokładnie przyjrzysz się tym parametrom, to odkryjesz, iż są one identyczne z polami struktury MDICREATESTRUCT z wyjątkiem hWndParent. Co istotne, jest to ten sam parametr, który występuje z wiadomością WM_MDICREATE. Struktura MDICREATESTRUCT nie posiada pola hWndParent, ponieważ i tak musisz przekazać całą strukturę do właściwego okna klienckiego za pomocą SendMessage.
W tym miejscu zastanawiasz się: której metody powinienem użyć? Czym się one różnią? Oto odpowiedź:
Metoda WM_MDICREATE może tworzyć jedynie okna potomne MDI w tym samym wątku jako od wywołujący. Na przykład, jeśli twoja aplikacja posiada dwa wątki, a pierwszy tworzy okno ramowe MDI, to jeśli drugi wątek chce utworzyć potomka MDI, musi to zrobić za pomocą wywołania CreateMDIChild: wysłanie wiadomości WM_MDICREATE do pierwszego wątku nie będzie działać. Jeśli twoja aplikacja ma tylko jeden wątek, możesz użyć dowolnej metody.
Nieco więcej szczegółów wymaga opis procedury okna w potomku MDI. Podobnie jak w przypadku okna ramowego, nie wolno wywoływać w niej DefWindowProc do przetworzenia nie obsługiwanych wiadomości, lecz DefMDIChildProc. Funkcja ta ma dokładnie takie same parametry co DefWindowProc. Oprócz wiadomości WM_MDICREATE istnieje wiele innych wiadomości związanych z oknami MDI. Wymieniam je poniżej:
Ta wiadomość może zostać wysłana przez aplikację do okna klienckiego w celu aktywacji przez nie wybranego okna potomnego MDI. Gdy okno klienckie odbierze wiadomość, uaktywnia wybrane okno potomne MDI i wysyła wiadomość WM_MDIACTIVATE do okna potomnego dezaktywowanego i aktywowanego. Wiadomość ma dwukrotne zastosowanie: może być używana przez aplikację do aktywacji pożądanego okna potomnego i może być użyta przez samo okno potomne MDI jako wskaźnik jego aktywacji / dezaktywacji. Na przykład, jeśli każde okno MDI posiada inne menu, może wykorzystać tę sposobność do zmiany menu okna ramowego przy swojej aktywacji / dezaktywacji.
Wiadomości obsługi układu, aranżacji okien potomnych MDI. Ma przykład, jeśli chcesz, aby okna potomne MDI ułożyły się kaskadowo jedno za drugim, wyślij do ich okna klienckiego wiadomość WM_MDICASCADE.
Wyślij tę wiadomość do okna klienckiego w celu usunięcia okna potomnego MDI. Powinieneś korzystać z tej wiadomości zamiast wywoływania DestroyWindow, ponieważ w przypadku, gdy usuwane okno jest zmaksymalizowane, wiadomość ta przywróci oryginalny tytuł oknu ramowemu. Jeśli zastosujesz DestroyWindow, tytuł okna ramowego nie będzie odtworzony.
Wyślij tę wiadomość, aby otrzymać uchwyt aktualnie aktywnego okna potomnego MDI.
Wiadomość WM_MDIMAXIMIZE maksymalizuje wybrane okno potomne MDI, a WM_MDIRESTORE przywraca je do stanu poprzedniego. Dla tych operacji zawsze wykorzystuj te wiadomości. Jeśli użyjesz funkcji ShowWindow z SW_MAXIMIZE, okno potomne MDI zostanie poprawnie zmaksymalizowane, lecz problemy pojawią się przy próbie powrotu do stanu poprzedniego. Z minimalizacją okna potomnego MDI przy pomocy ShowWindow nie ma jednakże problemów.
Wyślij tę wiadomość do okna klienckiego, aby aktywować następne lub poprzednie okno potomne MDI zgodnie z zawartością parametrów wParam i lParam.
Wyślij tę wiadomość do okna klienckiego w celu odświeżenia menu okna ramowego. Uwaga: po wysłaniu tej wiadomości musisz wywołać funkcję DrawMenuBar, aby odświeżyć pasek menu.
Wyślij tę wiadomość do okna klienckiego w celu zastąpienia całego menu okna ramowego lub tylko podmenu okna. Musisz użyć tej wiadomości zamiast SetMenu. Po wysłaniu wiadomości musisz wywołać DrawMenuBar, aby odświeżyć pasek menu. Zwykle stosujemy tę wiadomość, gdy aktywne okno potomne MDI posiada swoje własne menu i chcesz nim zastąpić menu okna ramowego.
Podsumujmy teraz kroki tworzenia aplikacji MDI:
Plik MDI.ASM
.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 "Potomek MDI", 0
AppName DB "Przykład MDI w Win32asm", 0
ClosePromptMessage DB "Jesteś pewny, że chcesz zamknąć to okno?", 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
;=================================
; Rejestrujemy klasę okna ramowego
;=================================
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
;======================================
; Rejestrujemy klasę okna potomnego MDI
;======================================
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==0
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, eax, 1
mov ClientStruct.hWindowMenu, eax
mov ClientStruct.idFirstChild, 100
INVOKE CreateWindowEx, NULL, ADDR MDIClientName, NULL,
WS_CHILD OR WS_VISIBLE OR WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
hWnd, NULL, hInstance, ADDR ClientStruct
mov hwndClient, eax
;=====================================
; Inicjujemy strukturę MDICREATESTRUCT
;=====================================
mov mdicreate.szClass, OFFSET MDIChildClassName
mov mdicreate.szTitle, OFFSET MDIChildTitle
push hInstance
pop mdicreate.hOwner
mov eax, CW_USEDEFAULT
mov mdicreate.x, eax
mov mdicreate.y, eax
mov mdicreate.lx, eax
mov mdicreate.ly, eax
.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,
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_SKIPDISABLED, 0
.ELSEIF ax==IDM_NEW
INVOKE SendMessage, hwndClient, WM_MDICREATE,
0, ADDR mdicreate
.ELSEIF ax==IDM_CLOSE
INVOKE SendMessage, hwndClient, WM_MDIGETACTIVE, 0, 0
INVOKE SendMessage, eax, WM_CLOSE, 0, 0
.ELSE
INVOKE DefFrameProc, hWnd, hwndClient, uMsg, wParam, lParam
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, hChildMenu, edx
.ELSE
INVOKE GetSubMenu, hMainMenu, 1
mov edx, eax
INVOKE SendMessage, hwndClient, WM_MDISETMENU, hMainMenu, edx
.ENDIF
INVOKE DrawMenuBar, hwndFrame
.ELSEIF uMsg==WM_CLOSE
INVOKE MessageBox, hChild, ADDR ClosePromptMessage,
ADDR AppName, MB_YESNO
.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
Plik MDI.RC
#include "RESOURCE.H"
#define IDR_MAINMENU 101
#define IDR_CHILDMENU 102
#define IDM_EXIT 40001
#define IDM_TILEHORZ 40002
#define IDM_TILEVERT 40003
#define IDM_CASCADE 40004
#define IDM_NEW 40005
#define IDM_CLOSE 40006
IDR_MAINMENU MENU DISCARDABLE
{
POPUP "&Plik"
{
MENUITEM "&Nowy", IDM_NEW
MENUITEM "&Koniec", IDM_EXIT
}
POPUP "&Okno"
{
MENUITEM "Ułóż przy sobie poziomo", IDM_TILEHORZ
MENUITEM "Ułóż przy sobie pionowo", IDM_TILEVERT
MENUITEM "Ułóż jedno na drugim", IDM_CASCADE
}
}
IDR_CHILDMENU MENU DISCARDABLE
{
POPUP "&Plik (potomek)"
{
MENUITEM "&Nowy", IDM_NEW
MENUITEM "&Zamknij",IDM_CLOSE
MENUITEM "&Koniec", IDM_EXIT
}
POPUP "&Okno (potomek)"
{
MENUITEM "Ułóż przy sobie poziomo", IDM_TILEHORZ
MENUITEM "Ułóż przy sobie pionowo", IDM_TILEVERT
MENUITEM "Ułóż jedno na drugim", IDM_CASCADE
}
}
Pierwszą rzeczą wykonywaną przez ten program jest zarejestrowanie klasy okna ramowego i klasy okna potomnego MDI. Po wykonaniu tych czynności program wywołuje funkcję CreateWindowEx do utworzenia okna ramowego. Wewnątrz sekcji obsługi wiadomości WM_CREATE okna ramowego tworzymy okno klienckie:
LOCAL ClientStruct: CLIENTCREATESTRUCT
.IF uMsg==WM_CREATE
INVOKE GetMenu, hWnd
mov hMainMenu, eax
INVOKE GetSubMenu, eax, 1
mov ClientStruct.hWindowMenu, eax
mov ClientStruct.idFirstChild, 100
INVOKE CreateWindowEx, NULL, ADDR MDIClientName, NULL,
WS_CHILD OR WS_VISIBLE OR \
WS_CLIPCHILDREN, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, hWnd, NULL, hInstance,
ADDR ClientStruct
mov hwndClient, eax
Wywołana zostaje funkcja GetMenu w celu pobrania uchwytu do menu okna ramowego, który ma być użyty jako parametr dla wywołania GetSubMenu. Do GetSubMenu przekazujemy wartość 1, ponieważ podmenu do wstawienia listy okien jest drugim podmenu. Następnie wypełniamy pola struktury CLIENTCREATESTRUCT. Nie musimy tego robić w tym miejscu, lecz tak jest wygodnie.
mov mdicreate.szClass, OFFSET MDIChildClassName
mov mdicreate.szTitle, OFFSET MDIChildTitle
push hInstance
pop mdicreate.hOwner
mov eax, CW_USEDEFAULT
mov mdicreate.x, eax ;tutaj wszędzie trafia wartość
mov mdicreate.y, eax ;CW_USEDEFAULT
mov mdicreate.lx, eax
mov mdicreate.ly, eax
Po utworzeniu okna ramowego (i również klienckiego) wywołujemy funkcję LoadMenu do załadowania menu dla okna potomnego z zasobów. Musimy dostać uchwyt tego menu, aby go wymienić z menu okna ramowego, gdy pojawi się okno potomne. Nie zapomnij wywołać funkcji DestroyMenu z tym uchwytem przed zakończeniem działania aplikacji i powrotem do systemu Windows. Zwykle system automatycznie usuwa menu skojarzone z oknem, gdy aplikacja kończy pracę, lecz w tym przypadku menu okna potomnego nie jest powiązane z żadnym oknem, stąd wciąż będzie zajmowało cenną pamięć, nawet po zakończeniu aplikacji (jest to tzw. wyciek pamięci - memory leak).
INVOKE LoadMenu, hInstance, IDR_CHILDMENU
mov hChildMenu, eax
...
INVOKE DestroyMenu, hChildMenu
Wewnątrz pętli wiadomości wywołujemy funkcję TranslateMDISysAccel.
.WHILE TRUE
INVOKE GetMessage, ADDR msg, NULL, 0, 0
.BREAK .IF (!eax)
INVOKE TranslateMDISysAccel, hwndClient, ADDR msg
.IF eax==0
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDIF
.ENDW
Jeśli TranslateMDISysAccel zwraca w rejestrze eax wartość różną od 0, to znaczy, iż dana wiadomość została już wewnętrznie obsłużona przez sam system Windows, zatem tobie nie pozostaje nic do zrobienia. Jeśli funkcja zwróci wartość 0, to wiadomość nie dotyczy MDI i powinna zostać obsłużona w zwykły sposób.
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
Zwróć uwagę, iż wewnątrz procedury okna w oknie ramowym wywołujemy DefFrameProc do obsługi pozostałych wiadomości.
Wołem roboczym procedury okna jest sekcja obsługi wiadomości WM_COMMAND. Gdy użytkownik wybiera opcję menu Nowy, tworzymy okno potomne MDI.
.ELSEIF ax==IDM_NEW
INVOKE SendMessage, hwndClient, WM_MDICREATE,
0, ADDR mdicreate
W naszym przykładzie tworzymy okno potomne MDI wysyłając wiadomość WM_MDICREATE do okna klienckiego przekazując mu w iParam wskazanie procedury MDICREATESTRUCT.
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, hChildMenu, edx
.ELSE
INVOKE GetSubMenu, hMainMenu, 1
mov edx, eax
INVOKE SendMessage, hwndClient, WM_MDISETMENU, hMainMenu, edx
.ENDIF
INVOKE DrawMenuBar, hwndFrame
Po utworzeniu okna potomnego MDI monitoruje ono wiadomość WM_MDIACTIVATE w celu sprawdzenia, czy jest oknem aktywnym. Dokonuje tego porównując wartość lParam zawierającą uchwyt aktywnego okna ze swoim własnym uchwytem. Jeśli są równe, to jest aktywnym oknem i następnym krokiem będzie wymiana menu okna ramowego na jego własne. Ponieważ oryginalne menu zostanie zastąpione, należy ponownie powiadomić system Windows, w którym podmenu ma się pojawiać lista okien. Stąd musimy wywołać ponownie funkcję GetSubMenu w celu pobrania uchwytu do podmenu. Wysyłamy wiadomość WM_MDISETMENU do okna klienckiego, aby osiągnąć pożądany wynik. Parametr wParam wiadomości WM_MDISETMENU zawiera uchwyt menu, którym ma zostać zastąpione menu oryginalne. lParam zawiera uchwyt podmenu, w którym ma się pojawić lista okiem MDI. Zaraz po wysłaniu wiadomości WM_MDISETMENU wywołujemy funkcję DrawMenuBar, aby odświeżyć listę menu, w przeciwnym wypadku będziesz miał bałagan w menu.
.ELSE
INVOKE DefMDIChildProc, hChild, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
ChildProc ENDP
Wewnątrz procedury okna w oknie potomnym MDI musisz przekazać wszystkie nie obsługiwane wiadomości do DefMDIChildProc zamiast do DefWindowProc.
.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_SKIPDISABLED, 0
Gdy użytkownik wybierze jeden z elementów menu w podmenu Okno, wysyłamy odpowiednią wiadomość do okna klienckiego. Jeśli użytkownik wybierze ułożenie okien jedno obok drugiego, wysyłamy wiadomość WM_MDITILE do okna klienckiego określając w wParam rodzaj pożądanego ułożenia okien. Wiadomość WM_CASCADE jest podobna.
.ELSEIF ax==IDM_CLOSE
INVOKE SendMessage, hwndClient, WM_MDIGETACTIVE, 0, 0
INVOKE SendMessage, eax, WM_CLOSE, 0, 0Gdy użytkownik wybierze element menu Zamknij, musimy pobrać uchwyt bieżąco aktywnego okna potomnego MDI najpierw wysyłając wiadomość WM_MDIGETACTIVE do okna klienckiego. Wartość zwrotna w rejestrze eax jest uchwytem do tego okna. Po tej operacji wysyłamy wiadomość WM_CLOSE do aktywnego okna MDI.
.ELSEIF uMsg==WM_CLOSE
INVOKE MessageBox, hChild, ADDR ClosePromptMessage,
ADDR AppName, MB_YESNO
.IF eax==IDYES
INVOKE SendMessage, hwndClient, WM_MDIDESTROY, hChild, 0
.ENDIF
Wewnątrz procedury okna w oknie potomnym MDI, gdy zostaje odebrana wiadomość WM_CLOSE, wyświetlamy okienko informacyjne z prośbą o potwierdzenie operacji zamykania okna. Jeśli odpowiedź jest twierdzącą, wysyłamy wiadomość WM_MDIDESTROY do okna klienckiego. WM_MDIDESTROY zamyka okno potomne MDI i przywraca tytuł w oknie ramowym.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe