MDI to skrót od angielskiego
Multiple Document Interface i jest to po prostu takie coś, gdzie mamy okienka w okienku. W MSDN możemy przeczytać, że MDI jest sposobem tworzenia aplikacji zorientowanych pod kątem pracy z wieloma dokumentami jednocześnie. Jednak nie ma co rozgryzać takie definicje, najlepiej zobaczyć przykład takiej aplikacji:
Podstawy MDI
Jak to zwykle bywa w WinApi, żeby korzystać z MDI trzeba nieco zachodu. Na początek trzeba dodać nową klasę okna. Standardowo aplikacja MDI ma zdefiniowane dwie klasy okna. Klasę okna głównego i klasę okna dziecka. My musimy dodać tą drugą. Okno dziecko jest właśnie tym okienkiem w okienku. Trzeba więc zmodyfikować tworzenie klasy okna w funkcji
WinMain:
WNDCLASSEX wincl;
wincl.hInstance = hInstance;
wincl.lpszClassName = "KlasaOkna";
wincl.lpfnWndProc = WindowProcedure;
wincl.style = 0;
wincl.cbSize = sizeof( WNDCLASSEX );
wincl.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
wincl.hIconSm = LoadIcon( hInstance, IDI_APPLICATION );
wincl.hCursor = LoadCursor( NULL, IDC_ARROW );
wincl.lpszMenuName = NULL;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hbrBackground =( HBRUSH ) COLOR_WINDOW;
if( !RegisterClassEx( & wincl ) )
return FALSE;
wincl.lpfnWndProc = ChildWindowProcedure;
wincl.lpszMenuName =( LPCTSTR ) NULL;
wincl.lpszClassName = "KlasaOknaDziecka";
if( !RegisterClassEx( & wincl ) )
return FALSE;
Początkującym może się wydać dość dziwne, że używamy tej samej zmiennej
wincl do utworzenia obydwu klas. Zamiast powtarzać ten sam kod po prostu zmieniamy kilka niezbędnych pól. Kolor okna czy kursor po zostają bez zmian, więc po co pisać dwa razy ten sam kod? Funkcji
RegisterClassEx nie będzie to przeszkadzać, gdyż klasy mają inne nazwy (pole
lpszClassName).
Oczywiście funkcję
ChildWindowProcedure trzeba zadeklarować. Deklaracja jest taka sama jak w przypadku "normalnej" procedury okna:
LRESULT CALLBACK WindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
LRESULT CALLBACK ChildWindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
Pętla Komunikatów
W programie używającym MDI, trzeba także zmodyfikować pętlę komunikatów, tak by rozróżnić komunikaty idące do okna głównego od tych idących do okna-dziecka MDI. Tak więc standardową pętlę komunikatów zastępujemy tym:
while( GetMessage( & messages, NULL, 0, 0 ) ) {
if( !TranslateMDISysAccel( hMDIClient, & messages ) ) {
TranslateMessage( & messages );
DispatchMessage( & messages );
}
}
Funkcja
TranslateMDISysAccel tłumaczy (przekształca) komunikaty
WM_KEYDOWN na
WM_SYSCOMMAND i wysyła je do aktywnego okna-dziecka MDI. Jeżeli jest to komunikat do okna głównego ("normalnego") to funkcja zwraca
FALSE.
Użyliśmy tutaj uchwytu
hMDIClient. Dobrze będzie go mieć w zmiennej globalnej, dlatego na początku programu (poza funkcją
WinMain dorzuć tą deklarację:
HWND hMDIClient;
#define ID_MDI_FIRSTCHILD 50000
Klient MDI
W poprzednim podrozdziale zadeklarowaliśmy uchwyt
hMDIClient. Jak sama nazwa wskazuje, będzie w nim siedział klient MDI. Tylko co to za ustrojstwo jest? Klient MDI to po prostu kontrolka, wewnątrz której wyświetlają się okna-dzieci MDI. Jak praktycznie do wszystkiego w WinApi, przy tworzeniu klienta potrzebna będzie struktura. Nazwy się ona
CLIENTCREATESTRUCT. Oto pola tej struktury, które będą nas interesować:
Ponieważ klient jest zwyczajną kontrolką, tworzymy go funkcją
CreateWindowEx. Jako jej ostatni parametr podajemy wskaźnik do struktury
CLIENTCREATESTRUCT. Poniższy kod należy umieścić po instrukcji tworzącej główne okno:
CLIENTCREATESTRUCT ccs;
ccs.hWindowMenu = NULL;
ccs.idFirstChild = ID_MDI_FIRSTCHILD;
hMDIClient = CreateWindowEx( 0, "MDICLIENT",( LPCTSTR ) NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL,
5, 5, 640, 480, hwnd,( HMENU ) 0xCAC, hInstance,( LPSTR ) & ccs );
ShowWindow( hMDIClient, SW_SHOW );
Omówmy ten kod po kolei. Najpierw definiujemy nowy obiekt struktury
CLIENTCREATESTRUCT. Potem przypisujemy polom wartości. Do pola
hWindowMenu podajemy na razie
NULL. Menu w MDI zajmiemy się w dalszej części kursu. Do pola
idFirstChild wstawiamy zdefiniowaną wcześniej stałą
ID_MDI_FIRSTCHILD. To właśnie od tej liczby włącznie Windows będzie nadawał ID kolejnym oknom-dzieciom MDI. Następnie tworzymy klienta MDI. Nazwa klasy to
MDICLIENTM. Dorzuciliśmy flagę
WS_CLIPCHILDREN, która uniemożliwi zamalowywanie okien-dzieci przez okno główne. Jako ostatni argument podajemy wskaźnik do struktury
CLIENTCREATESTRUCT. Na koniec pokazujemy klienta MDI funkcją
ShowWindow.
Procedura zdarzeniowa okna-dziecka
Ważną rzeczą, na którą trzeba zwrócić uwagę pisząc aplikacje MDI jest właśnie procedura zdarzeniowa okna-dziecka (u nas
ChildWindowProcedure). Różni się ona nieco od procedury zdarzeniowej "normalnego" okna. Przede wszystkim, komunikaty, z których nie korzystamy odsyłamy za pomocą funkcji
DefMDIChildProc. Wywołujemy ją z takimi samymi argumentami jak
DefWindowProc - różnica praktycznie tylko w nazwie.
Jak zwykle Microsoft zadbał o to by wkurzyć programistów - i oto mamy: W procedurze zdarzeniowej okna-dziecka jeżeli chcemy obsłużyć komunikat
WM_CHILDACTIVATE (jest on wysyłany gdy okno-dziecko dostanie focusa, gdy jest aktywowane, przenoszone lub zmieniamy jego rozmiar) to musimy go i tak zwrócić funkcją
DefMDIChildProc do systemu. Jeżeli tego nie zrobimy, okienka nie będą mogły mieć focusa.
LRESULT CALLBACK ChildWindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch( message ) {
default:
return DefMDIChildProc( hwnd, message, wParam, lParam );
}
return 0;
}
Procedura zdarzeniowa okna głównego
W procedurze zdarzeniowej okna głównego też zachodzą zmiany. Zamiast funkcji
DefWindowProc do zwracania nieobsłużonych komunikatów używamy funkcji
DefFrameProc w taki sposób:
DefFrameProc( hwnd, hMDIClient, message, wParam, lParam );
Jest jeszcze jeden szkopuł. Mianowicie gdy używamy funkcji
DefFrameProc przy próbie zmiany rozmiaru okna, klient MDI jest rozciągany na całe okno. Czasem jest to sytuacja pożądana, czasem nie. Na szczęście można łatwo rozwiązać ten problem, dodając obsługę komunikatu
WM_SIZE (nawet pustą). W przykładzie który zostanie omówiony w tym artykule nie będziemy potrzebować rozciągać klienta MDI na cały ekran, dlatego zastosujemy wspomniany trick.
Gotowy Szablon
Najwyższy czas poskładać nasz kod do kupy. Można tego używać jako szablonu, do tworzenia własnych aplikacji MDI:
#include <windows.h>
#define ID_MDI_FIRSTCHILD 50000
LRESULT CALLBACK WindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
LRESULT CALLBACK ChildWindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
char szClassName[ ] = "KlasaOkna";
char szChildName[ ] = "KlasaOknaDziecka";
HWND hMDIClient;
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) {
HWND hwnd;
MSG messages;
WNDCLASSEX wincl;
wincl.hInstance = hInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure;
wincl.style = 0;
wincl.cbSize = sizeof( WNDCLASSEX );
wincl.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
wincl.hIconSm = LoadIcon( hInstance, IDI_APPLICATION );
wincl.hCursor = LoadCursor( NULL, IDC_ARROW );
wincl.lpszMenuName = NULL;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hbrBackground =( HBRUSH ) COLOR_WINDOW;
if( !RegisterClassEx( & wincl ) )
return FALSE;
wincl.lpfnWndProc = ChildWindowProcedure;
wincl.lpszMenuName =( LPCTSTR ) NULL;
wincl.lpszClassName = "KlasaOknaDziecka";
if( !RegisterClassEx( & wincl ) )
return FALSE;
hwnd = CreateWindowEx(
0,
szClassName,
"Aplikacja MDI",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
400,
300,
NULL,
NULL,
hInstance,
NULL
);
ShowWindow( hwnd, nCmdShow );
CLIENTCREATESTRUCT ccs;
ccs.hWindowMenu = NULL;
ccs.idFirstChild = ID_MDI_FIRSTCHILD;
hMDIClient = CreateWindowEx( 0, "MDICLIENT",( LPCTSTR ) NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL,
5, 5, 400, 300, hwnd,( HMENU ) 0xCAC, hInstance,( LPSTR ) & ccs );
ShowWindow( hMDIClient, SW_SHOW );
while( GetMessage( & messages, NULL, 0, 0 ) ) {
if( !TranslateMDISysAccel( hMDIClient, & messages ) ) {
TranslateMessage( & messages );
DispatchMessage( & messages );
}
}
return messages.wParam;
}
LRESULT CALLBACK WindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch( message ) {
case WM_DESTROY:
PostQuitMessage( 0 );
break;
case WM_SIZE:
break;
default:
return DefFrameProc( hwnd, hMDIClient, message, wParam, lParam );
}
return 0;
}
LRESULT CALLBACK ChildWindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch( message ) {
default:
return DefMDIChildProc( hwnd, message, wParam, lParam );
}
return 0;
}
Dodawanie okienek
W poprzednim rozdziale dużo się napracowaliśmy, a osiągnęliśmy niewiele (jak to w WinApi ;-)). Otrzymaliśmy zaledwie pusty obszar klienta MDI. Nadszedł czas, by do zmienić. Zajmiemy się dodawaniem okienek. Żeby nie robić tego na bezdurno, przygotujemy prosty edytor plików tekstowych wykorzystujący MDI. Na początku dorzucimy kilka stałych na początku programu:
HWND hMDIClient;
HWND hNew, hOpen, hSave, hMDIEdit; #define ID_MDI_FIRSTCHILD 50000
#define ID_NEW 30222 #define ID_OPEN 30223 #define ID_SAVE 30224 #define ID_MDI_EDIT 2000
Mamy tam identyfikatory przycisków nowy, otwórz, zapisz oraz pola do edycji tekstu. Teraz dodamy do naszego okna przycisk "Nowy":
hNew = CreateWindowEx( 0, "BUTTON", "Nowy", WS_CHILD | WS_VISIBLE,
660, 5, 150, 30, hwnd,( HMENU ) ID_NEW, hInstance, NULL );
Nowe okno-dziecko MDI
Nadszedł czas na obsłużenie kliknięcia takiego przycisku. Teraz dochodzimy do sedna sprawy - dodawania nowych okienek MDI. Wykorzystamy do tego strukturę
MDICREATESTRUCT, do której wpiszemy nieco informacji o naszym nowym okienku. Omówmy niektóre pola tej struktury:
W polach
x,
y,
cx,
cy można podać wartośc
CW_USEDEFAULT. Wtedy Windows sam wybierze pozycje i rozmiar dla naszych okienek. W parametrze
lParam możemy przechowywać wskaźnik na dowolne, wybrane przez nas dane.
Samo dodanie okienka odbywa się poprzez wysłanie komunikatu
WM_MDICREATE do klienta MDI. Jako parametr
lParam podajemy adres struktury
MDICREATESTRUCT. Komunikat zwraca uchwyt do nowo utworzonego okna. Skoro już to wiemy, możemy obsłużyć kliknięcie przycisku "Nowy":
case WM_COMMAND: {
switch( LOWORD( wParam ) ) {
case ID_NEW: {
MDICREATESTRUCT mcs;
HWND hChild;
mcs.szTitle = "Nowe okno MDI";
mcs.szClass = szChildName; mcs.hOwner = GetModuleHandle( NULL ); mcs.x = mcs.cx = CW_USEDEFAULT; mcs.y = mcs.cy = CW_USEDEFAULT; mcs.style = MDIS_ALLCHILDSTYLES;
hChild =( HWND ) SendMessage( hMDIClient, WM_MDICREATE, 0,( LONG ) & mcs );
if( !hChild ) {
MessageBox( hwnd, "Nie udało się utworzyć okienka MDI!", "A to szkoda...",
MB_ICONEXCLAMATION | MB_OK );
}
}
break;
}
}
break;
W powyższym kodzie tworzymy i wypełniamy strukturę
MDICREATESTRUCT i wysyłamy komunikat do klienta MDI, by dodać okno. Funkcja
GetModuleHandle służy do pobrania instancji aplikacji.
Najwyższy czas skompilować program, poklikać parę razy przycisk i podziwiać rezultat:
Wypełniamy kontrolkami okienka MDI
Prędzej czy później znudzą ci się puste okna MDI, które dotychczas udało nam się zrobić. Przydało by się wypełnić okno kontrolkami. Nasuwa się jednak pytanie: w którym miejscu programu to zrobić? Przecież nasze okno jest tworzone naprędce po kliknięciu przycisku. Odpowiedź brzmi: W procedurze zdarzeniowej okna-dziecka MDI! A konkretniej, należy obsłużyć komunikat
WM_CREATE. Jest on wysyłany, gdy okno jest tworzone. Tego właśnie szukaliśmy! Bierzmy się wiec do roboty.
Do naszego okna dodamy na razie dwie kontrolki:
Dodawanie kontrolek w procedurze zdarzeniowej nie różni się praktycznie niczym od tych dodawanych w
WinMain. No może poza jednym. Jeżeli nie mamy gdzieś globalnego uchwytu do instancji naszego programu, to musimy skorzystać z funkcji
GetModuleHandle. Możemy teraz przystąpić do kodzenia. Procedura zdarzeniowa okna-dziecka MDI będzie wyglądać teraz tak:
LRESULT CALLBACK ChildWindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch( message ) {
case WM_CREATE: {
hMDIEdit = CreateWindowEx( WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE |
WS_BORDER | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL, 0, 0, 400, 150,
hwnd,( HMENU ) ID_MDI_EDIT, GetModuleHandle( NULL ), NULL );
hSave = CreateWindowEx( 0, "BUTTON", "Zapisz", WS_CHILD | WS_VISIBLE,
5, 160, 150, 30, hwnd,( HMENU ) ID_SAVE, GetModuleHandle( NULL ), NULL );
SetFocus( GetDlgItem( hwnd, ID_MDI_EDIT ) );
}
break;
default:
return DefMDIChildProc( hwnd, message, wParam, lParam );
}
return 0;
}
W powyższym kodzie mamy zdefiniowaną obsługę komunikatu
WM_CREATE. Dodajemy tam dwie kontrolki: EDIT'a i przycisk. Następnie ręcznie ustawiamy focusa na edicie. Użyliśmy funkcji
SetFocus. Użyliśmy też funkcji
GetDlgItem do pobrania naszego EDIT'a. Dlaczego? Ano dlatego, żeby odróżnić go od innych EDIT'ów z innych okien-dzieci MDI. Zauważ, że pierwszym argumentem tej funkcji jest uchwyt do okna (w tym przypadku okna-dziecka MDI).
No to teraz kompilujemy nasz program i cieszymy się naszym nowym dziełem:
Komunikaty od okien-dzieci MDI
Najwyższy czas sprawić, by przycisk zapisz zapisywał plik. Jeżeli nie przeczytałeś jeszcze o
dialogach zapisu pliku proponuje zrobić to teraz. Wracając do naszego problemu - komunikaty od kontrolek w oknach MDI wysyłane są do ich procedury zdarzeniowej, tak jak w przypadku "normalnych" okien. W takim razie do naszej procedury dialogowej okna-dziecka MDI dodajemy taki case:
case WM_COMMAND: {
switch( LOWORD( wParam ) ) {
case ID_SAVE:
OPENFILENAME ofn;
char szNazwaPliku[ MAX_PATH ];
ZeroMemory( & ofn, sizeof( OPENFILENAME ) );
ofn.lStructSize = sizeof( ofn );
ofn.hwndOwner = HWND_DESKTOP;
ofn.lpstrFilter = "Pliki tekstowe (*.txt)\0*.txt\0Wszystkie pliki (*.*)\0*.*\0";
ofn.lpstrFile = szNazwaPliku;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrDefExt = "txt";
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
OFN_OVERWRITEPROMPT;
if( GetSaveFileName( & ofn ) ) {
DWORD dlugosc = GetWindowTextLength( GetDlgItem( hwnd, ID_MDI_EDIT ) );
if( !dlugosc ) break;
LPSTR szTextZEdita =( LPSTR ) GlobalAlloc( GPTR, dlugosc + 1 );
GetWindowText( GetDlgItem( hwnd, ID_MDI_EDIT ), szTextZEdita, dlugosc + 1 );
HANDLE hFile;
bool bSuccess = false;
hFile = CreateFile( szNazwaPliku, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 );
if( hFile != INVALID_HANDLE_VALUE ) {
DWORD dwWritten;
if( WriteFile( hFile, szTextZEdita, dlugosc, & dwWritten, NULL ) )
bSuccess = true;
}
CloseHandle( hFile );
GlobalFree( szTextZEdita );
if( !bSuccess ) {
MessageBox( hwnd, "Nie udało się zapisać pliku!",
"A to szkoda...", MB_ICONEXCLAMATION | MB_OK );
}
}
break;
}
}
break;
Przede wszystkim dodaliśmy instrukcje dla przycisku "Zapisz" (
ID_SAVE). Korzystamy ze struktury
OPENFILENAME i funkcji
GetSaveFileName by użyć dialogu zapisu pliku. Szczegółowo opisane jest to
tutaj. Następnie sprawdzamy długość tekstu z pola tekstowego (funkcja
GetWindowTextLength) i pobieramy ten tekst (funkcja
GetWindowText). Szczegółowo opisane jest to
tutaj. Potem otwieramy plik funkcją
CreateFile i zapisujemy do niego zawartość EDIT'a funkcją
WriteFile. Szczegółowo opisane jest to
tutaj.
Układanie okien
W kontrolce klienta MDI okienka-dzieci można ustawiać (a to ci dopiero nowość ;-)). Wystarczy wziąć naszego gryzonia i trochę się naklikać. Co ciekawe Windows umożliwia samopoukładanie się okien MDI. Wszystko sprowadza się do wysłania odpowiednich komunikatów do klienta MDI. Oto kilka najczęściej używanych ustawień:
Kaskada
Aby poukładać okna w kaskadę należy wysłać komunikat do klienta MDI:
SendMessage( hMDIClient, WM_MDICASCADE, 0, 0 );
Oto jak wygląda taka kaskada:
Aranżuj ikony
Nazwa zupełnie nietrafiona. Ta opcja spowoduje poukładanie zminimalizowanych okienek, jeżeli wystąpiły jakieś "dziury". Aby zaaranżować ikony należy wysłać do klienta MDI komunikat
WM_MDIICONARRANGE:
SendMessage( hMDIClient, WM_MDIICONARRANGE, 0, 0 );
Dopasuj w poziomie
Po angielsku nazywa się to "tile" co oznacza w dosłownym tłumaczeniu płytkę ceramiczną. Słowo "dopasowanie" lepiej tu pasuje. Żeby dopasować okienka w poziomie należy wysłać do klienta MDI komunikat
WM_MDITILE, a w parametrze
wParam podać wartość
MDITILE_HORIZONTAL:
SendMessage( hMDIClient, WM_MDITILE, MDITILE_HORIZONTAL, 0 );
Oto jak wyglądają tak dopasowane okna:
Dopasuj w pionie
Brat bliźniak poprzedniego. Gdy jako parametr
wParam podamy wartość
MDITILE_VERTICAL:
SendMessage( hMDIClient, WM_MDITILE, MDITILE_VERTICAL, 0 );
Osiągniemy taki efekt
Wiele okien - różne klasy
W naszym programie używaliśmy takie same okienka MDI. Nic nie stoi jednak na przeszkodzie, by tworzyć różne okna czyli z różnych klas okna. Posłuży nam do tego prosty przykład: zrobimy okno MDI z narzędziami, w którym będą zawarte opcje układania okien z poprzedniego rozdziału. Tak więc do dzieła! Zaczniemy od dorzucenia dodatkowej klasy okna na nasze okno:
WNDCLASSEX wincl;
wincl.lpfnWndProc = ToolChildWindowProcedure;
wincl.lpszMenuName =( LPCTSTR ) NULL;
wincl.lpszClassName = "KlasaOknaZNarzedziami";
if( !RegisterClassEx( & wincl ) )
return FALSE;
Nasze okno będzie mieć swoją procedurę zdarzeniową, więc dorzućmy jej deklarację na początku programu:
LRESULT CALLBACK ToolChildWindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
#define ID_CASCADE 32888
#define ID_TILEHOR 32889
#define ID_TILEVER 32887
Przy okazji dodaliśmy identyfikatory przycisków do naszego okienka. Teraz możemy napisać jego procedurę zdarzeniową. Mamy tu zebraną większość rzeczy, których dotychczas się nauczyliśmy:
LRESULT CALLBACK ToolChildWindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch( message ) {
case WM_CREATE: {
CreateWindowEx( 0, "BUTTON", "Kaskada", WS_CHILD | WS_VISIBLE,
5, 5, 150, 30, hwnd,( HMENU ) ID_CASCADE, GetModuleHandle( NULL ), NULL );
CreateWindowEx( 0, "BUTTON", "Dopasuj poziomo", WS_CHILD | WS_VISIBLE,
5, 40, 150, 30, hwnd,( HMENU ) ID_TILEHOR, GetModuleHandle( NULL ), NULL );
CreateWindowEx( 0, "BUTTON", "Dopasuj pionowo", WS_CHILD | WS_VISIBLE,
5, 75, 150, 30, hwnd,( HMENU ) ID_TILEVER, GetModuleHandle( NULL ), NULL );
}
break;
case WM_COMMAND: {
switch( LOWORD( wParam ) ) {
case ID_CASCADE:
SendMessage( hMDIClient, WM_MDICASCADE, 0, 0 );
break;
case ID_TILEHOR:
SendMessage( hMDIClient, WM_MDITILE, MDITILE_HORIZONTAL, 0 );
break;
case ID_TILEVER:
SendMessage( hMDIClient, WM_MDITILE, MDITILE_VERTICAL, 0 );
break;
}
}
break;
default:
return DefMDIChildProc( hwnd, message, wParam, lParam );
}
return 0;
}
Kodu nie będę tłumaczył, bo praktycznie nie zawiera niczego nowego. Najwyższy czas dodać nasze okno do klienta MDI. Ten kod umieszczamy za instrukcją tworzącą klienta MDI:
MDICREATESTRUCT mcs;
HWND hChild;
mcs.szTitle = "Narzędzia";
mcs.szClass = "KlasaOknaZNarzedziami"; mcs.hOwner = GetModuleHandle( NULL );
mcs.x = mcs.cx = CW_USEDEFAULT;
mcs.y = mcs.cy = CW_USEDEFAULT;
mcs.style = MDIS_ALLCHILDSTYLES;
hChild =( HWND ) SendMessage( hMDIClient, WM_MDICREATE, 0,( LONG ) & mcs );
if( !hChild ) {
MessageBox( hwnd, "Nie udało się utworzyć okienka z narzędziami!",
"A to szkoda...", MB_ICONEXCLAMATION | MB_OK );
}
W polu
szClass struktury
MDICREATESTRUCT wprowadzamy nazwę klasy. Nie musi ona być taka sama dla wszystkich okien. W naszym przypadku, to
"KlasaOknaZNarzedziami". Wszystko inne wygląda tak samo jak w przypadku tworzenia innych okien MDI.
Teraz kompilujemy nasz kod i podziwiamy rezultaty:
Menu a MDI
W aplikacji MDI Windows może za nas odwalić część brudnej roboty. Może dodać obsługę okienek MDI przez menu. Najpierw jednak trzeba takie menu mieć. Zróbmy więc proste menu:
200 MENU {
POPUP "&Program" {
MENUITEM "&Nowe okno", 30222
}
}
Jak widać jest tam tylko jedna pozycja obsługująca dodawanie nowego okna MDI. Oczywiście nasze menu trzeba jeszcze dodać do okna głównego. Jeżeli tego nie umiesz, przeczytaj artykuł o
menu.
Zakładam, że menu już jest dodane do okna. Teraz wróćmy na chwilę do struktury
CLIENTCREATESTRUCT. Mieliśmy w niej pole
hWindowMenu do którego mogliśmy przypisać uchwyt do menu. Na początku wpisaliśmy tam
NULL. Teraz wpiszemy tam to:
ccs.hWindowMenu = GetSubMenu( GetMenu( hwnd ), 0 );
Do poprawnego działania pozycji z menu potrzebne jest zmodyfikowane obsługi komunikatu
WM_COMMAND w oknie głównym w taki sposób:
case WM_COMMAND: {
switch( LOWORD( wParam ) ) {
case ID_NEW:
break;
default: {
if( LOWORD( wParam ) >= ID_MDI_FIRSTCHILD ) {
DefFrameProc( hwnd, hMDIClient, message, wParam, lParam );
}
}
}
}
break;
Teraz już chyba wiesz po co trzeba było podać ID pierwszego okna przy tworzeniu klienta MDI. Właśnie komunikaty o tym ID (i wyższym dla kolejnych okien) są wysyłane, jeżeli wybierzemy pozycję z menu. Wtedy przekazujemy komunikat systemowi (funkcja
DefFrameProc), który wybierze odpowiednie okno.
I to wszystko! Zobaczmy jeszcze jak nasze menu będzie wyglądać: