Część III
Zagadnienia zaawa nsowane
Rozdział 19
In '
te rf
es
wi I
e od
ok
umentow
Interfejs wielodokumentowy (Multiple-Document Interface, MDI) stanowi specyfi-
kację dla aplikacji obsługujących dokumenty systemu Microsoft Windows. Spe-
cyfikacja ta opisuje strukturę okna oraz interfejs użytkownika, umożliwiające
korzystanie z wielu dokumentów z poziomu jednej aplikacji (na przykład doku-
mentów tekstowych w edytorze lub tabel w arkuszu kalkulacyjnym). Mówiąc
prosto, podobnie jak Windows pozwala otwierać wiele okien aplikacji, tak apli-
kacja MDI pozwala otwierać wiele okien dokumentów w jednym obszarze robo-
czym. Pierwszą aplikacją MDI dla Windows była pierwsza windowsowa wersja
programu Microsoft Excel. Wkrótce po niej pojawiły się kolejne.
Koncepcja MDI
Choć specyfikacja MDI funkcjonuje już od czasów Windows 2.0, nie od razu ją
wykorzystywano, ponieważ początkowo pisanie aplikacji MDI było trudne i wy-
magało bardzo szeroko zakrojonych prac programistycznych. Od pojawienia się
Windows 3.0 większość uciążliwych zadań przejął system. Podwaliny tego sys-
temu, wzbogacone o rozszerzenia z Windows 95, zostały przeniesione do Win-
dows 98 i Microsoft Windows NT.
Elementy MDI
Główne okno programu MDI ma tradycyjny wygląd: zawiera pasek tytułu, menu,
obramowanie, którym można regulować wielkość okna, ikonę menu systemowe-
go, a także przyciski minimalizacji, maksymalizacji i zamykania. Obszar klienta
jest jednak nazywany często obszarem roboczym i nie służy bezpośrednio do
wyświetlania danych. Obszar roboczy może zawierać okna potomne; w każdym
z nich może być wyświetlany jeden dokument.
Okna potomne przypominają zwykłe okna aplikacyjne, czyli między innymi głów-
ne okno programu MDI. One także zawierają paski tytułu, obramowanie umoż-
liwiające regulowanie wielkości okna, ikonę menu systemowego, przyciski mini-
malizacji, maksymalizacji i zamykania oraz ewentualnie paski przewijania. Żad-
ne z okien dokumentów nie ma jednak własnego menu. Operacje na oknach do-
kumentów wykonuje się z głównego menu aplikacji.
1044 Część III: Zagadnienia zaawansowane
W każdej chwili aktywne pozostaje tylko jedno okno dokumentu (można je roz-
poznać po zmienionym w stosunku do innych okien kolorze paska tytuhx oraz
po tym, że znajduje się na wierzchu pozostałych okien dokumentów). Wszystkie
okna potomne dokumentów są kadrowane do wielkości obszaru roboczego i ni-
gdy nie wystają poza okno aplikacji.
Z punktu widzenia programisty Windows specyfikacja MDI wydaje się począt-
kowo dość przejrzysta. Wystarczy utworzyć okno WS CHILD dla każdego do-
kumentu, czyniąc jego oknem nadrzędnym główne okno aplikacji. Jednak po
bardziej dogłębnej analizie istniejących aplikacji MDI pojawią się pewne kompli-
kacje - fragmenty bardziej złożonego kodu.
ł Okno dokumentu MDI można minimalizować. Zminimalizowane okno do-
kumentu jest przedstawiane u dołu obszaru roboczego w postaci małego pa-
ska tytułu z ikoną. Na ogół aplikacja MDI zawiera inne definicje ikon dla głów-
nego okna aplikacji i inne dla poszczególnych typów okien dokumentów.
ł Okno dokumentu MDI moźna maksyxnalizować. Zmaksymalizowane okno
dokumentu jest pozbawione paska tytułu (który służy zazwyczaj jako miejsce
na nazwę pliku lub dokumentu), tekst wyświetlany dotąd na pasku okna do-
kumentu jest dopisywany do tekstu paska okna aplikacji. Ikona menu syste-
mowego okna dokumentu staje się pierwszym elementem paska menu okna
aplikacji. Przycisk zamykający okno dokumentu staje się ostatnim elementem
paska menu okna aplikacji i trafia na jego prawy skraj.
ł Klawisz skrótu zamykający okno dokumentu jest prawie identyczny z klawi-
szem skrótu zamykającym okno aplikacji, z tym że pierwszy jest kombinacją
zawierającą klawisz [Ctrl], a drugi kombinacją zawierającą klawisz [Alt]. Kon-
kretnie rzecz ujmując, okno aplikacji można zamknąć za pomocą [Alt+F4],
a okno dokumentu za pomocą [Ctrl+F4]. Dodatkowo dostępny jest skrót
[Ctrl+F6], który pozwala przełączać aktywne okna bieżącej aplikacji MDI.
[Alt+Spacja] wywołuje menu systemowe głównego okna - jak zawsze. [Alt+-
(minus)] wywołuje menu systemowe okna aktywnego dokumentu.
ł Przechodząc przez kolejne pozycje paska menu za pomocą klawiszy strzałek,
zazwyczaj pokonuje się poszczególne polecenia menu głównego oraz ikonę
menu systemowego (pierwszy element paska menu). W aplikacji MDI stero-
wanie z menu sterującego aplikacji jest przekazywane dodatkowo do menu
sterującego aktywnego dokumentu (stanowiącego - jak wspomniano wyżej -
pierwszą pozycję paska menu).
ł Jeżeli aplikacja może obsłużyć kilka typów okien potomnych (na przykład
Microsoft Excel obsługuje dokumenty z arkuszami kalkulacyjnymi i dokumenty
z wykresami), menu powinno odzwierciedlać operacje skojarzone z bieżącym
typem dokumentu. Wymaga to przełączania przez program menu przy wy-
borze (uaktywnieniu) dokumentu innego typu. Dodatkowym wymogiem jest
zawężenie dostępnych opcji menu - w przypadku gdy nie jest otwarty żaden
dokuxnent - do wyłącznie tych, które mają sens w programie bez otwartego
dokumentu, czyli na przykład do poleceń służących do tworzenia nowych
dokumentów i otwierania istruejących.
Rozdział 19: Interfejs wielodokumentowy 1045
ł W menu głównym znajduje się pozycja o nazwie Okno. Tradycyjnie jest to
ostatnia pozycja menu głównego, no, przedostatnia, jeżeli uwzględnić pozy-
cję Pomoc. Podmenu Okno zawiera opcje pomocne przy definiowaniu ukła-
du okien obszaru roboczego. Okna dokumentów mogą być ułożone kaskado-
wo od lewego górnego rogu do prawego dolnego albo sąsiadująco (czyli tak,
że żadne z okien nie jest przykryte innym). To podmenu zawiera również li-
stę wszystkich otwartych okien dokumentów. Wybranie jednego z okien po-
woduje uaktywnienie go (przesunięcie na pierwszy plan).
Każdy z powyższych aspektów MDI jest obsługiwany przez Windows 98. Nie-
które funkcje wymagają rueco większego nakładu pracy (jak zostanie pokazane
w programie przykładowym), ale nakład ten i tak jest nieporównywalnie mniej-
szy od tego, jaki należałoby ponieść, aby poszczególne funkcje zaimplemento-
wać samodzielnie.
Obsługa MDI
Przed omówieniem obsługi MDI przez Windows MDI trzeba zaprezentować kil-
ka nowych pojęć. Główne okno aplikacji jest nazywane oknem ramowym (frame
window) i stanowi, podobnie jak w tradycyjnych programach Windows, okno typu
WS OVERLAPPEDWINDOW,
Aplikacja MDI musi również utworzyć okno-klienta, bazując na predefiniowanej
klasie okien MDICLIENT. Okno-klienta tworzy się za pomocą wywołania Create-
Window z wymieruoną wyżej klasą okna i stylem WS CHILD. Ostatnim argumen-
tem funkcji CreateWindow jest wskaźnik do małej struktury typu CLIENTCREATE-
STRUCT. Okno-klient zajmuje obszar roboczy okna ramowego i jest odpowiedzial-
ne za większość obsługi MDI. Kolor tego okna-klienta jest identyczny z kolorem
COLOR APPWORKSPACE.
Okna dokumentów, jak można się zorientować, są nazywane oknami potomny-
mi. Tworzy się je, inicjując strukturę typu MDICREATESTRUCT i wysyłając oknu-
klientowi komunikat WMMDICREATE ze wskaźnikiem do tej struktury.
Okna dokumentów są potomkami okna-klienta, które z kolei jest potomkiem okna
ramowego. Omawianą hierarchię okien można obejrzeć na rysunku 19-1.
T
1046 Część III: Zagadnienia zaawansowane
okno ramowe
(glówne okno aplikacji)
..
okno-klient
Rysunek 19-1. Hierarchia okien w aplikacji MDI
(dokument
windows)
Klasa okna (i procedura okna) jest potrzebna nie tylko dla okna ramowego, ale
i dla każdego okna otomnego obsługiwanego przez aplikację. Okno-klient nie
wymaga procedury okna, ponieważ jego klasa jest już wstępnie zarejestrowana.
Obsługa MDI w Windows 98 obejmuje definicje: klasy okna, pięciu funkcji, dwóch
struktur danych i dwunastu komunikatów. Wspomniałem już, że klasa nowego
okna to MDICLIENT, a struktury danych to CLIENTCREATESTRUCT i MDICRE-
ATESTRUCT. W aplikacjach MDI funkcję DefWindowProc zastępują dwie z pięciu
funkcji: zamiast wywoływać dla wszystkich nieobsłużonych komunikatów funk-
cję Def tNindowProc, procedura okna ramowego wywohxje funkcję DefFrameProc,
a procedura okna potomnego - funkcję DefMDIChildProc. Inna funkcja typowa dla
MDI, TransIateMDISysAccel, jest używana w taki sam sposób jak TransIateAccele-
rator, omówiona w rozdziale 10. Obsługa MDI obejmuje również funkcję Arran-
gelconicWindows, ale jeden z k"munikatów specjalnych MDI sprawia, że w pro-
gramach MDI staje się ona zbyteczna.
Piątą funkcją MDI jest CreateMDlWindow. Umożliwia ona tworzenie okna potom-
nego w osobnym wątku. Funkcja ta nie przydaje się w programach jednowątko-
wych, co zaprezentuję w dalszej części książki.
W programie przykładowym, który przedstawię dalej, zademonstruję dziewięć
z dwunastu komunikatów MDI (pozostałe trzy są na ogół zbędne). Wszystkie
zaczynają się prefiksem WM MDI. Okno ramowe wysyła je do okna-klienta, aby
wykonać jakąś operację na oknie potomnym albo uzyskać od niego jakieś dane
(okno ramowe może na przykład wysłać do okna potomnego komunikat
WM MDICREATE, powodujący utworzenie okna potomnego). Wyjątkiem jest
komunikat WM MDIACTIVATE: choć okno ramowe może wysyłać go do okna-
klienta w celu uaktywniania okien potomnych, okno-klient może go wysyłać rów-
nież do uaktywnianych lub dezaktywowanych okien potomnych w celu poinfor-
mowania ich o powstałych zmianach.
Rozdział 19: Intertejs wislodorcumen#awy 1047
Prrykładowa impieentaeja MDI
Program MDIDEMO, pokazany na rysunku 19-2, demonstruje podstawy pisania
aplikacji MDI.
' MDIDEMO.C
/*
mnutru.u - Uemonstracja interfejsu wielodokumentowego (MDI)
(c) Charles Petzold, 1998
*/
dinclude
#include "resource.h"
4define INIT MENUPOS 0
4define HELLO MENUPOS 2
define RECT MENU POS 1
4define IDM FIRSTCHILD 50000
, LRESULT CALLBACK Frame4ndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK CloseEnumProc (HWND, LPARAM) ;
LRESULT CALLBACK HelloWndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK RectWndProc (HWND, UINT, WPARAM, LPARAM) ;
// struktura przeznaczona na prywatne dane każdego z okien potomnych Hello
typedef struct tagHELLODATA
UINT iColor ; _
COLORREF clrText ;
1
HELLODATA, * PHELLODATA ;
// struktura przeznaczona na prywatne dane każdego z okien potomnych Rect
typedef struct tagRECTDATA
f
short cxClient ;
short cyClient ;
1
RECTDATA, * PRECTDATA ;
// zmienne globalne
TCHAR szAppName[] = TEXT f"MDIDemo") ;
TCHAR szframeClass[] = TEXT ("MdiFrame") ;
TCHAR szHelloClass[] = TEXT ("MdiHelloChild") ;
TCHAR szRectClass[] = TEXT ("MdiRectChild") ;
HINSTANCE hInst ;
I
HMENU hMenuInit, hMenuHello, hMenuRect ;
HMENU hMenuInitWindow, hMenuHelloWindow, hMenuRectWindow ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInsta.nce,
PSTR szCmdLine, int iCmdShow)
(
1048 Czść III: Zagadnienis zaawansowane
(ciąg dalszy ze strony 1047)
HACCEL hAccel ;
HWND hwndFrame. hwndClient :
MSG msg :
WNDCLASS wndclass :
hInst = hInstance ;
// Rejestrowanie klasy okna ramowego
wndclass.style CS HREDRAW CS VREDRAW ;
wndclass.lpfnWndProc = FrameWndProc :
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon a LoadIcon (NULL, IDI_APPLICATION)
wndclass.hCursor s LoadCursor (NULL, IDC_ARROW) :
wndclass.hbrBackground = (HBRUSH) (COLOR APPWORKSPACE + 1)
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szFrameClass ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MBICONERROR) ;
return 0 ;
// Rejestrowanie klasy okna potomnego Hello
wndclass.style = CS_HREDRAW CS VREDRAW :
wndclass.lpfnWndProc = HelloWndProc ;
wndclass.cbClsExtra = 0 :
wndclass.cbWndExtra = sizeof (HANDLE) :
wndclass.hInstance = hInstanee ;
wndclass.hIcon = LoadIcon (NULL. IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL. IDC RROW) :
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITĘ BRUSH)
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szHelloClass :
RegisterClass (&wndclass) ;
// Rejestrowanie klasy okna potomnego Rect
wndclass.style = CS HREDRAW CS VREDRAW ;
wndclass.lpfnWndProc = RectWndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = sizeof (HANDLE) :
wndclass.hInstance = hInstance :
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor ł LoadCursor (NULL. IDC RROW) :
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH)
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szRectClass :
RegisterClass (&wndclass) ;
Rozdział 19: Intartejs wielodokumentowy
// Pozyskanie uchwytów do trzech menu & podmenu
hMenuInit = LoadMenu (hInstance, TEXT ("MdiMenuInit")) ;
hMenuHello = LoadMenu (hInstance, TEXT ("MdiMenuHello")) ;
hMenuRect = LoadMenu (hInstance, TEXT ("MdiMenuRect")) ;
' hMenuInitWindow GetSubMenu (hMenuInit, INIT_MENU_POS) ;
hMenuHelloWindow = GetSubMenu (hMenuHello, HELLO_MENUPOS) ; ,.ł
hMenuRectWindow = GetSubMenu (hMenuRect. RECT MENUPOS) ;
// Wczytanie tablicy skrótów klawiaturowych
hAccel = LoadAccelerators (hInstance, szAppName) ;
, // Utworzenie okna ramowego
I
hwndFrame = CreateWindow (szFrameClass, TEXT ("MDI Demonstration"),
' WS OIIERLAPPEDWINDOW WS_CLIPCHILDREN,
j
CW USEDEFAULT, CW USEDEFAULT,
CW USEDEFAULT, CW USEDEFAULT,
NULL, hMenuInit, hInstance, NULL) ;
hwndClient = GetWindow (hwndFrame, GW CHILD) ;
ShowWindow (hwndFrame, iCmdShow) ;
UpdateWindow (hwndFrame) ;
// Wejście do zmodyfikowanej pętli komunikatów
while (GetMessage (&msg, NULL, 0, 0))
1
if (!TranslateMDISysAccel (hwndClient, &msg) &&
!TranslateAccelerator (hwndFrame, hAccel, &msg))
(
TranslateMessage (&msg) ;
' DispatchMessage (&msg) ;
)
1
// Sprzątanie poprzez usuwanie niepodłdczonych menu
DestroyMenu (hMenuHello) ;
DestroyMenu (hMenuRect) ;
return msg.wParam ;
LRESULT CALLBACK FrameWndProc (HWND hwnd. UINT message,
WPARAM wParam, LPARAM lParam)
static HWND hwndClient ;
CLIENTCREATESTRUCT clientcreate ;
HWND hwndChild ;
I MDICREATESTRUCT mdicreate ;
switch (message)
f
case WM CREATE: // Utworzenie okna-klienta
1050 Czść 1: ZsdMenia zaaaaansowane
(ciąg dalszy ze strony 1049)
r
clientcreate.hWindowMenu = hMenuInitWindow ;
clientcreate.idFirstChild = IDMFIRSTCHILD ;
hwndClient = CreateWindow (TEXT ("MDICLIENT"), NULL,
WS_CHILD ( WS_CLIPCHILDREN WS_VISIBLE, '
0, 0, 0, 0, hwnd, (HMENU) l, hInst,
(PSTR) &clientcreate) ;
return 0 ;
case WM COMMAND:
switch (LOWORD (wParam))
case IDMFILE NEWHELLO: // Utworzenie okna potomnego Hello
mdicreate.szClass = szHelloClass ;
mdicreate.szTitle = TEXT ("Hello") ;
mdicreate.hOwner = hInst ; ,
mdicreate.x = CW USEDEFAULT :
mdicreate.y = CWUSEDEFAULT ;
mdicreate.cx = CW USEDEFAULT ;
mdicreate.cy = CW USEDEFAULT ;
mdicreate.style = 0 ;
mdicreate.lParam = 0 ;
hwndChild = (HWND) SendMessage (hwndClient,
WM_MDICR^ATE, 0,
(LPARAM) (LPMDICREATESTRUCT) &mdicreate) ;
return 0 ;
case IDMFILE NEWRECT: // Utworzenie okna potomnega Rect
r
mdicreate.szClass = szRectClass ;
mdicreate.szTitle = TEXT ("Rectangles") ;
mdicreate.hOwner = hInst ; ,
mdicreate.x = CW_USED.EFAULT ;
mdicreate.y = CW_USEDEFAULT ;
mdicreate.cx = CW_USEDEFAULT :
mdicreate.cy = CW USEDEFAULT ;
mdicreate.style = 0 ;
mdicreate.lParam = 0 :
hwndChild = (HWND) SendMessage (hwndClient,
WM_MDICREATE, 0,
(LPARAM) (LPMDICREATESTRUCT) &mdicreate)
return 0 :
case IDMFILECLOSE: // Zamykanie aktywnego okna
hwndChild = (HWND) SendMessage (hwndClient,
W1_MDI6ETACTIV^, 0, 0) ;
,
if (SendMessage (hwndChild, WM_OUERYENDSESSION, 0, 0))
SendMessage (hwndClient, WM_MDIDESTROY,
(WPARAM) hwndChild, 0) :
return 0 ;
case IDM PPEXIT: // Kończenie programu
Rozdział 19: Interfejs wielodokumentowy 1051
SendMessage (hwnd, WMCLOSE, 0, 0) ;
return 0 ;
// komunikat modyfikacji ukladu okien
case IDM_WINDOW_TILE:
SendMessage (hwndClient, WM_MDITILE, 0, 0) ;
return 0 ;
case IDM_WINDOW CASCADE:
SendMessage (hwndClient, WM MDICASCADE, 0, 0) ;
return 0 ;
case IDM WINDOW ARRANGE:
SendMessage (hwndClient, WM MDIICONARRANGE, 0, 0) ;
return 0 ;
case IDM WINDOWCLOSEALL: // Próba zamknięcia wszystkich potomków
EnumChildWindows (hwndClient, CloseEnumProc, 0) ;
return 0 ;
default: // Przekazanie do aktywnego okna potomnego... i
I
hwndChild = (HWND) SendMessage (hwndClient,
WM MDIGETACTIVE, 0, 0) ;
if (IsWindow (hwndChild))
SendMessage (hwndChild, WM COMMAND, wParam, lParam) ;
break ; // ..,a potem do DefFrameProc
)
break ;
case WM OUERYENDSESSION:
case WMCLOSE: // Próba zamknięcia wszystkich potomków
SendMessage (hwnd, WM COMMANO, IDM WINDOW CLOSEALL, 0) ;
if (NULL != GetWindow (hwndClient, GW CHILD))
return 0 ;
i
break ; // tj. wywolanie DefFrameProc
case WM DESTROY:
PostOuitMessage (0) ;
return 0 ;
// Przekazanie nieobsłużonych komunikatów do DefFrameProc
! // (nie do DefWindowProc)
7 return DefFrameProc (hwnd, hwndClient, message, wParam, lParam)
I 1
BOOL CALLBACK CloseEnumProc (HWND hwnd, LPARAM lParam)
if (GetWindow (hwnd, GW OWNER)) // Sprawdzenie tytulu ikony
return TRUE ;
;łl
1052 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1051)
SendMessage (GetParent (hwnd), WM MDIRESTORE, (WPARAM) hwnd, 0> ;
if (!SendMessage (hwnd, WM OUERYENDSESSION, 0, 0))
return TRUE ;
SendMessage (GetParent (hwnd), WM MDIDESTROY, (WPARAM) hwnd, 0) ;
return TRUE ;
1
LRESULT CALLBACK HelloWndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
(
static COLORREF clrTextArrayC] = ( RGB (0, 0, 0), RGB (255, 0, 0),
RGB (0, 255, 0), RGB ( 0, 0, 255),
RGB (255, 255, 255) 1 ;
static HWND hwndClient, hwndFrame ;
HDC hdc ;
HMENU hMenu ;
PHELLODATA pHelloData ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
t
case WM_CREATE:
// Zarezerwowanie pamięci na prywatne dane okna
pNelloData = (PHELLODATA) HeapAlloc (GetProcessHeap (),
HEAP ZERO_MEMORY, sizeof (HELLODATA)) ;
pNelloData->iColor = IDM_COLOR_BLACK ;
pHelloData->clrText = RGB (0, 0, 0) ;
SetWindowLong (hwnd, 0, (long) pHelloData) ;
// Zachowanie niektórych uchwytów okien
hwndClient = GetParent (hwnd) ;
hwndFrame = GetParent (hwndClient) ;
return 0 ;
case WM COMMAND: ,
switch (LOWORD (wParam))
(
case IDM_COLOR_BLACK:
case IDM_COLOR_RED:
case IDM_COLOR_GREEN:
case IDM COLOR BLUE:
case IDM COLOR WHITE:
// Zmiana koloru tekstu
pNelloData = (PHELLODATA) GetWindowLong (hwnd, 0) ;
hMenu = GetMenu (hwndFrame) ;
CheckMenuItem (hMenu, pHelloData->iColor, MF UNCHECKED) ;
pHelloData->iColor = wParam ;
Rozdział 19: Interfejs wielodokumentowy 1053
CheckMenuItem (hMenu, pHelloData->iColor, MF CHECKED) ;
pHelloData->clrText = clrTextArrayCwParam - IDM COLORBLACK] ;
InvalidateRect (hwnd, NULL, FALSE) ;
)
return 0 ; "
case WMPAINT:
// Malowanie okna
hdc = BeginPaint (hwnd, &ps) ;
pHelloOata = (PHELLODATA) GetWindowLong (hwnd, 0) ;
SetTextColor (hdc, pHelloData->clrText) ;
GetClientRect (hwnd, &rect) ; ;
DrawText (hdc, TEXT ("Hello, World!"). -1, &rect,
DT SINGLELINE ,DT CENTER DT VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_MDIACTIVATE:
// Ustaw menu Hello w przypadku otrzymania fokusu
if (lParam == (LPARAM) hwnd)
SendMessage (hwndClient, WM MDISETMENU,
(WPARAM) hMenuHello, (LPARAM) hMenuHelloWindow) ;
// Postaw lub usuń znaczek obok opcji menu
pHelloData = (PHELLODATA) GetWindowLong (hwnd, 0) ;
CheckMenuItem (hMenuHello, pHelloData->iColor, a,
(lParam == (LPARAM) hwnd) ? MF CHECKED : MF UNCHECKED) ; ?;
// Ustaw menu Init w przypadku utraty fokusu
if (lParam != (LPARAM) hwnd)
SendMessage (hwndClient, WM MDISETMENU, (WPARAM) hMenuInit,
(LPARAM) hMenuInitWindow) ;
DrawMenuBar (hwndFrame) ;
i..
return 0 ; s
case WM_OUERYENDSESSION:
case WM_CLOSE:
if (IDOK != MessageBox (hwnd, TEXT ("OK to close window ").
TEXT ( Hello"), "
MBICONOUESTION MB OKCANCEL))
return 0 ;
break ; // tj. wywolanie DefMDIChildProc
case WM_DESTROY:
pHelloData = (PHELLODATA) GetWindowLong (hwnd, 0) :
HeapFree (GetProcessHeap (), 0, pHelloData) ;
1054 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1053)
return 0 ; ,
I
// Przekazanie nieobsłużonych komunikatów do DefMDIChildProc
return DefMDIChildProc (hwnd, message, wParam, lParam) ;
1
r
LRESULT CALLBACK RectWndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
(
static HWND hwndClient, hwndFrame ;
HBRUSH hBrush ;
HDC hdc ;
PRECTDATA pRectData ;
PAINTSTRUCT ps ;
int xLeft, xRight, yTop, yBottom ;
r
short nRed, nGreen, nBlue ;
switch (message) r
i
case WMCREATE:
// Zarezerwowanie pamięci na prywatne dane okna
pRectData = (PRECTDATA) HeapAlloc (GetProcessHeap (),
HEAP ZERO MEMORY, sizeof (RECTDATA)) ;
SetWindowLong (hwnd, 0, (long) pRectData) ;
// Uruchomienie czasomierza
SetTimer (hwnd, 1, 250, NULL) ;
// Zachowanie niektórych uchwytów okien
hwndClient = GetParent (hwnd) ;
hwndFrame = GetParent (hwndClient) ;
return 0 ;
case WM SIZE: // Jeżeli nie zminimalizowane, zapisanie rozmiaru okna
if (wParam != SIZĘ MINIMIZED)
(
pRectData = (PRECTDATA) GetWindowLong (hwnd, 0) ;
pRectOata->cxClient = LOWORD (lParam) ;
pRectOata->cyClient = HIWORD (lParam) ;
)
break ; // WMSIZE musi być obslużony przez DefMDIChildProc
case WM TIMER: // Wyświet.lenie losowego prostokta
pRectData = (PRECTDATA) GetWindowLong (hwnd, 0) ;
r
xLeft = rand () % pRectData->cxClient ;
xRight = rand () % pRectOata->cxClient ;
yTop = rand () % pRectOata->cyClient ;
yBottom = rand () % pRectData->cyClient ;
nRed = rand () & 255 ;
Rozdział 19: In#ertejs wielodokumen#owy 1055
,
nGreen = rand () & 255 ;
nBlue = rand () & 255 ;
hdc = GetDC (hwnd) ;
hBrush = CreateSolidBrush (RGB (nRed, nGreen, nBlue)) ;
SelectObject (hdc, hBrush) ;
Rectangle (hdc, min (xLeft, xRight), min (yTop, yBottom),
max (xLeft, xRight), max (yTop, yBottom)) ;
ReleaseDC (hwnd, hdc) ;
Delete0bject (hBrush) ;
i return 0 ;
case WM_PAINT: // Wyczyszczenie okna
InvalidateRect (hwnd, NULL, TRUE) ;
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd,, &ps) ;
return 0 ;
case W1'1_MDIACTIIIATE: // Ustawienie wlaściwego menu
if (lParam == (LPARAM) hwnd)
SendMessage (hwndClient, WM_MDISETMENU, (WPARAM) hMenuRect,
(LPARAM) hMenuRectWindow) ; '
else
SendMessage (hwndClient, WM_MDISETMENU, (WPARAM) hMenuInit, i
(LPARAM) hMenuInitWindow) ;
DrawMenuBar (hwndFrame) ;
return 0 ;
, case WM DESTROY:
pRectData = (PRECTDATA) GetWindowLong (hwnd, 0) ;
HeapFree (GetProcessHeap (), 0, pRectOata) ;
ł KillTimer (hwnd, 1) ;
return 0 :
l
// Przekazanie nieobslużonych kotunikatów do DefMDIChildProc
return DefMDIChildProc (hwnd, message, wParam, lParam) ;
l
IDiDM.RC (frignty?
//Microsoft Developer Studio generated resource script.
#include "resource.h"
? #include "afxres.h"
//////////////////////////////////////////////////////////////////////.///////
// Menu I
MDIMENUINIT MENU DISCARDABLE !
BEGIN
POPUP "File"
Czść III: Zagadnienia zaawansowane
(ciąg dulszy ze strony I055)
BEGIN
MENUITEM "New &Hel7o`, IDM_FILE-NEWHELLO
MENUITEM "New &Rectangle", IDM-FILE-NEWRECT
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM APP EXIT
END - -
END
MDIMENUHELLO MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New &Hello", IDM-FILE-NEWHELLO
MENUITEM "New &Rectangle", IDMFILĘ NEWRECT
MENUITEM "&Close", IDM-FILE-CLOSE
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM APP-EXIT
END - ,
POPUP "&Color"
BEGIN
MENUITEM "&Black", IDM COLOR_BLACK
MENUITEM "&Red", IDM COLOR-RED
MENUITEM "&Green", IDM COLOR GREEN
MENUITEM "B&lue", IDM COLOR_BLUE
MENUITEM "&White", IDM COLOR WHITE
END -
POPUP "&Window"
BEGIN
MENUITEM "&Cascade\tShift+F5", IDM-WINDOW_CASCADE
MENUITEM "&Tile\tShift+F4", IDM-WINDOW_TILE
MENUITEM "Arrange &Icons", IDM WINDOW_ARRANGE
MENUITEM "Close &All", IDM WINDOW CLOSEALL
END -
END
MDIMENURECT MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New &Hello", IDM-FILE_NEWHELLO
MENUITEM "New &Rectangle", IDM_FILE_NEWRECT
MENUITEM "&Close", IDM FILE-CLOSE
MENUITEM SEPARATOR -
MENUITEM "E&xit", IDM APP-EXIT
END -
POPUP "&Window"
BEGIN
MENUITEM "&Cascade\tShift+F5", IDM_WINDOW_CASCADE
MENUITEM "&Tile\tShift+F4", IDM_WINDOW TILE
MENUITEM "Arrange &Icons", IDM-WINDOW_ARRANGE
MENUITEM "Close &A11", IDM WINDOW CLOSEALL
END - ,
END
lllllll!lllllllllllllll!llll!llllllllllllllllllllllllllllllllllll!!ll!lllllll
// Accelerator
Rozdział 19: Interfejs wielodokumentowy 1057
MDIDEMO ACCELERATORS DISCARDABLE
BEGIN
UKF4, IDM WINDOW_TILE: UIRTKEY, SHIFT, NOINUERT
VKF5, IDM WINDOWCASCADE, VIRTKEY, SHIFT, NOINVERT
END
RESOURCE.H (firagmenty)
// Microsoft Developer Studio generated include file.
// Used by MDIDemo.rc
define IDM_FILE_NEWHELLO 40001
dldefine IDM FILE_NEWRECT 40002
4idefine IDM APP_EXIT 40003
ldefine IDM_FILE CLOSE 40004
#define IDM_COLOR BLACK 40005
define IDM_COLOR_RED 40006
define IDM_COLOR GREEN 40007
#define IDM_COLOR BLUE 40008
define IDM_COLOR WHITE 40009
define IDM_WINDOW_CASCADE 40010
define IDM WINDOW TILE 40011
define IDM_WINDOW ARRANGE 40012
define IDM WINDOW CLOSEALL 40013
Rysunek 19-2. Program MDIDEMO
Program MDIDEMO obsługuje dwa typy bardzo prostych okien dokumentów:
jeden służący do wyświetlania tekstu "Hello, World!" w środku obszaru robo-
czego, a drugi do wywietlania ciągu losowych prostokątów (w kodzie źródło-
wym oknom przypisano identyfikatory Hello i Rect). Z każdym z dwóch typów
okien skojarzone jest inne menu. Okno dokumentu, w którym wyświetlany jest
napis "Hello, World!", zawiera menu pozwalające zmieniać kolor tekstu.
Trzy menu
Przeanalizujmy najpierw skrypt zasobów o nazwie MDIDEMO.RC. Zawiera on
definicję trzech szablonów menu używanych w programie.
Jeżeli nie są otwarte żadne okna dokumentów, w programie aktywne jest menu
MdiMenuInit. Umożliwia ono tworzenie nowych dokumentów oraz kończenie
działania programu.
Z oknem dokumentu wyświetlającym napis "Hello, World!" jest skojarzone menu
MdiMenuHello. Podmenu Plik pozwala na otwieranie nowych dokumentów obu
typów, zamykanie aktywnych dokumentów i kończenie działania programu.
Podmenu Kolor umożliwia ustawiarue koloru tekstu. Podmenu Okno zawiera
polecenia służące do definiowania układu oki.en dokumentów (okna można ukła-
daE kaskadowo lub sąsiadująeo) oraz układu ikon dokumentów, a także do za-
mykarua wszystkich okien. To podmenu zawiera dodatkowo listę wszystkich
utworzonych okien dokumentów.
Część l: Zagadnienia zaawansowane
Menu MdiMenuRect jest skojarzone z oknem, w którym są wyświetlane losowe
prostokąty. Ma ono definicję zbliżoną do menu MdiMenuHello, tyle że nie za-
wiera podmenu Kolor.
Wszystkie identyfikatory menu są jak zwykle zdefiniowane w pliku nagłówko-
wym RESOURCE.H. W pliku MDIDEMO.C zdefiniowano oprócz tego trzy na-
stępujące stałe:
define INIT_MENU_POS
4define HELLO MENU_POS
idefine RECT MENUPOS
Powyższe identyfikatory określają położenie podmenu Okno w każdym z trzech
szablonów drzew menu. Dane te są potrzebne w programie do informowania
okna-klienta o miejscu, w którym ma się pojawiać wykaz dokumentów. Menu
MdiMenuInit nie zawiera, oczywiście, podmenu Okno, diatego miejsce wykazu
dokumentów zostało określone jako 0 (potencjalnie pojawiający się wykaz zosta-
nie dopisany do pierwszego podmenu). W rzeczywistości żaden wykaz nie zo-
stanie wygenerovany (dlaczego trzeba tak zrobić, okaże się daiej, gdy wyjaśnię
kanstrukcję i działanie programu).
Identyfikator IDM EIRSTCHILD, zdefiniowany w pliku MDIEMO.C, nie od-
powiada żadnej pozycji menu. Zostanie skojarzony z pierwszym oknem doku-
mentu z wykazu, który pojawi sig w podmenu Okno. Powinien być większy niż
wszystkie inne identyfikatory.
Procedura WinMan z płiku MDIDEMO.C zaczyna się od zarejestrowania klas
okien dla okna ramowego i dwóch okien potomnych. Procedury okien nazywają
się FrameWndProc, HetloWndProc i RectWndProc. Zazwyczaj z każdą klasą okien
powinna być związana inna ikona. Aby nie pogarszać przejrzys#aści przykładu,
zarówno dla ola ramowego, jak i dla okien ramowyeh została użyta standardo-
wa ikona IDI APPLICATION.
Zauważmy, że pole hbrBackground struktury WNDCLASS dla klasy okna rno-
wego zostało zdefiniowane jako COLOR APPWORKSPACE (kołor systemowy).
Nie jest to konieczne, ponważ cały obszar roboczy okna ramowego jest przy-
kryty przez okno-klienta, które i tak ma właśnie taki kolor. Zastosowanie tego
koloru daje jecak nieco lepszy rezultat, widvcy podczas pierwszego hryświe-
tlenia vkna raxnowego.
Pole tpszMenuName dla każdego z tych trzech klas okien ma przypisywarną war-
tość NULL. Dła klas okien po#onnnych He i ltect jQst to normalrse. Dła klasy
okna ramowego postanowiłen podczas twvrzenia okna wskazać uchwyt rzĄenu
w funkci CreateWindow.
W klasach okien Hello i Rect przewidziane jest dodatkowe miejsce na każde okno
używające niezerowej wartości pola cb Wnd^xBra struktury WNDCLASS. W miej-
scu tym znajdzie się wskaźnik odsyłający do bloku pamięci (o wielkości rówrtej
wielkości struktury HELLODATA lub RECTDATA, zdefniowanych na począ#ku
Rozdział 19: Interfejs wielodokumentowy 1059
pliku MDIDEMO.C) służącego do przechowywania prywatnych danych każde-
o z okien dokumentów.
g
Następnie w WinMain znajduje się wywołanie funkcji LoadMenu, służące załado-
waniu trzech menu i zapisaniu ich uchwytów w zmiennych globalnych. Trzy
wywołania GetSubMenu służą pozyskaniu uchwytów do podmenu menu Okno,
do którego zostanie dopisany wykaz dokumentów. Pozyskane uchwyty są także
zapisywane w zmiennych globalnych. Funkcja LoadAccelerators powoduje zała-
dowanie tablicy skrótów klawiaturowych.
Nowe okno ramowe jest powoływane do życia poprzez wywołanie funkcji Cre-
ateWindow z WinMain. Podczas działania FrameWndProc okno ramowe powołuje
do życia okno-klienta. Wymaga to kolejnego wywołania funkcji CreateWindow.
Klasa okna jest ustawiana na MDICLIENT, wstępnie zarejestrowaną klasę okien-
klientów MDI. W klasie MDICLIENT zawarta jest większość obsługi MDI syste-
mu Windows. Procedura okna-klienta służy jako warstwa pośrednicząca między
oknem ramowym i różnymi oknami dokumentów. Wywołując CreateWindow w ce-
lu utworzenia okna-klienta, jako ostatni argument należy podać wskaźnik do
struktury typu CLIENTCREATESTRUCT. Struktura ta ma dwa pola:
ł hWindowMenu - uchwyt podmenu, do którego zostanie dołączony wykaz do-
kumentów. W MDIDEMO jest nim hMenuInitWindow, uzyskany w WinMain.
Modyfikacje tego menu zostaną opisane dalej.
ł idFirstChild - identyfikator menu, które ma być skojarzone z pierwszym oknem
dokumentu z wykazu dokumentów. Jest nim po prostu IDM FIRSTCHILD.
W procedurze WinMain program wyświetla nowo utworzone okno ramowe i roz-
poczyna przetwarzanie pętli komunikatów. Pętla ta różni się nieco od normalnej:
program MDI przekazuje komunikat uzyskany z kolejki za pomocą wywołania
funkcji GetMessage do TransIateMDISysAccel (i do TranslateAccelerator, jeżeli - jak
MDIDEMO ma zdefiniowane skróty klawiaturowe).
Funkcja TranslateMDISysAccel tłumaczy wszelkie naciśnięcia klawiszy, które mogą
korespondować ze skrótami klawiaturowymi MDI (na przykład [Ctrl+F6)) na
komunikaty WM SYSCOMMAND. Jeżeli TranslateMDISysAccel albo TranslateAc-
celerator zwróci TRUE (co oznacza, że funkcja dokonała translacji komunikatu),
nie trzeba wywoływać TransIateMessage ani DispatchMessage.
Zwróć uwagę na dwa uchwyty okien przekazane do TranslateMDISysAccel i Trans-
lateAccelerator, odpowiednio: hwndClient oraz hwndFrame. Funkcja WinMain uzy-
skuje uchwyt okna hwndClient za pośrednictwem wywołania funkcji GetWindow
z argumentem GW CHILD.
Tworzenie okien potomnych
Większa część procedury FrameWndProc jest poświęcona przetwarzaniu komu-
nikatów WM COMMAND, sygnalizujących wybory menu. Numer identyfika-
cyjny menu zawiera jak zwykle młodsze słowo parametru wParam funkcji Fra-
meWndProc.
Dla numerów identyfikacyjnych menu IDM FILĘ NEWHELLO i IDM FI-
LĘ NEWRECT procedura FrameWńdProc musi tworzyć nowe okna dokumentów.
T
1060 Część III: Zagadnienia zaawansowane
Konstruuje się je, inicjując pola struktur typu MDICREATESTRUCT (których więk-
szość pól odpowiada argumentom CreateWindow) i wysyłając oknu-klientowi
komunikat 4VMMDICREATE z parametrem lParam ustawionym na wskaźnik do
tej struktury. Następnie okno-klient tworzy potomne okno dokumentu (można
też skorzystać z funkcji CreateMDIWindow).
Pole szTitle struktury MDICREATESTRUCT jest zazwyczaj nazwą pliku skojarzoną
z dokumentem. Polu stylu można nadać jeden ze stylów WS_HSCROLL lub
WS VSCROLL albo oba te style jednocześnie (co spowoduje umieszczenie w oknie
pasków przewijarua). Pole stylu może zawierać również znacznik WS MINIMI-
ZE lub WS MAXIMIZE, aby pierwotnie wyświetlone okno dokumentu było zmi-
nimalizowane lub zmaksymalizowane.
Pole IParam struktury MDICREATESTRUCT zapewnia oknu ramowemu i oknu-
klientowi sposób współużytkowania niektórych zmiennych. Powinno się mu
nadać wartość wskaźnika do bloku pamięci zawierającego strukturę. Podczas
przetwarzania komunikatu WMCREATE w potomnym oknie dokumentu IPa-
ram jest wskaźnikiem do struktury CREATESTRUCT, której pole IpCreateParams
jest z kolei wskaźnikiem do struktury MDICREATESTRUCT, która posłużyła do
utworzenia okna.
Po odebraniu komunikatu WMMDICREATE okno-klient tworzy potomne okno
dokumentu i dopisuje jego tytuł u dołu podmenu określonego w strukturze
MDICLIENTSTRUCT, która posłużyła do utworzenia okna-klienta. Kiedy pro-
gram MDIDEMO tworzy swoje pierwsze okno dokumentu, jest to podmenu Plik
menu MdiMenulnit. Jak wykaz dokumentów jest przenoszony do podmenu Okno
menu MdiMenuHello i MdiMenuRect, zostanie opisane później.
Wykaz dokumentów może zawierać maksymalnie 9 pozycji, z których każda jest
poprzedzana automatycznie podkreślonym numerem od 1 do 9. Jeżeli utworzo-
nych zostanie więcej dokumentów, po owych dziewięciu pozycjach pojawia się
jeszcze jedna, dodatkowa: Więcej okien. Wybranie jej otwiera okno dialogowe
z listą wszystkich okien dokumentów. Automatyczna (praktycznie bez udziału
programisty) obshzga wykazu dokumentów jest jednym z najprzyjemniejszych
elementów obshzgi MDI przez Windows.
Przetwarzanie komunikatu Wigcej okien
Zanim zajmiemy się potomnymi oknami dokumentów, pociągnijmy jeszcze tro-
chę wątek przetwarzania komunikatów w FrameWndProc.
Wybrarue pozycji Zamknij z menu Plik powoduje zamknięcie przez MDIDEMO
aktywnego okna potomnego. Uchwyt aktywnego okna potomnego program uzy-
skuje wysyłając oknu-klientowi komunikat WMMDIGETACTIVE. Jeżeli okno
potomne na komunikat WMQUERYENDSESSION odpowie twierdząco, wów-
czas MDIDEMO wysyła oknu-klientowi komunikat 4VMMDIDESTROY, zamy-
kający okno potomne.
Przetwarzanie opcji Zakończ z menu Plik wymaga tylko, aby procedura okna
ramowego wysłała sobie samej komunikat WMCLOSE.
Przetwarzanie opcji Sąsiadująco, Kaskadowo i Rozmieść ikony z podmenu Okno
Rozdział 19: interfejs wielodokumentowy 1061
jest proste i wymaga tylko wysyłania do okna-klienta odpowiednio komunika-
tów: 4VMMDITILE, WM MDICASCADE i WM MDIICONARRANGE.
Opcja Zamknij wszystkie jest trochę bardziej złożona, wymaga bowiem wywoła-
nia z FrameWndProc funkcji EnumChildWindows i przekazania jej wskaźnika do
funkcji CloseEnumProc. Funkcja ta wysyła do każdego z okien potomnych komu-
nikat WM MDIRESTORE oraz ewentualnie komunikaty WMQUERYENDSES-
SION i WM MDIDESTROY. Nie jest to realizowane dla okna zmirumalizowane-
go do postaci paska tytułu, rozpoznawanego przez niepustą (różną od NULL)
wartość zwrotną funkcji GetWindow wywołanej z argumentem GWOWNER.
Jak można zauważyć, w procedurze FrameWndProc nie są przetwarzane żadne
komunikaty WM COMMAND; które sygnalizują wybranie jednego z kolorów
menu Kolor. Komunikaty te są w rzeczywistości obsługiwane przez okno doku-
mentu. Z tego powodu procedura FrameWndProc przesyła wszellcie nieobshxżo-
ne komunikaty WMCOMMAND do aktywnego okna potomnego, aby mogło się
zająć tymi komunikatami, które go dotyczą.
Wszystkie komunikaty, których procedura okna ramowego postanowi nie obsłu-
giwać, muszą być przekazane do DefFrameProc. Funkcja ta zastępuje DefWindow-
Proc w procedurze okna ramowego. Jeżeli procedura okna ramowego przechwy-
ci choc'by jeden z komunikatów WM MENUCHAR, WM SETFOCUS lub WMSI-
ZE, i tak musi go przekazać do DefFrameProc.
Do procedury DefFrameProc muszą być również przekazywane nieobsłużone ko-
munikaty WM COMMAND. W szczególności w procedurze FrameWndProc nie
są przetwarzane żadne komunikaty WM COMMAND, których przyczyną jest
wybranie przez użytkownika któregoś dokumentu z listy podmenu Okno (war-
tości wParam dla tych opcji zaczynają się prefiksem IDM FIRSTCHILD). Komu-
nikaty te są przekazywane do DefFrameProc i tam przetwarzane.
Zauważ, że w oknie ramowym nie jest potrzebna żadna ewidencja uchwytów
okien dla tworzonych w nim dokumentów. Jeżeli kiedykolwiek uchwyty te oka-
żą się potrzebne (na przykład podczas przetwarzania opcji Zamknij wszystkie),
można je uzyskać za pomocą EnumChildWindows.
Okna potomne, okna dokumentów
Przyjrzyjmy się teraz procedurze HelloWndProc, która jest procedurą okna wyko-
rzystywaną przez okna dokumentów wyświetlające napis "Hello, World!"
Podobnie jak w każdej klasie okna używanej dla kilku okien jednocześnie, zmienne
statyczne zdefiniowane w procedurze okna (albo w dowolnej funkcji wywoły-
wanej z procedury okna) są wspólne dla wszystkich okien tworzonych na pod-
stawie tej klasy.
Dane prywatne każdego okna muszą więc być przechowywane jakoś inaczej niż
w zmiennych statycznych. Jedna ze stosowanych technik wykorzystuje właści-
wości okien. Inna - którą ja zastosowałem - wykorzystuje miejsce w pamięci
zarezerwowane przez zdefiniowanie niezerowej wartości w polu cbWndExtra
struktury WNDCLASS, służącej do rejestrowania klasy okna.
W programie MDIDEMO miejsca tego używam do przechowania wskaźnika do
1062 CZeść III: 2aaadnienia
bloku pamięci o rozmiarze HELLODATA. Miejsce na ten blok jest rezerwowane
w pamięci przez HeIloWndProc podczas rozpatrywania komunikatu WM CRE- !
ATE; wtedy również inicjowane są oba pola struktury (oznaczające obecnie wy-
braną opcję menu i kolor tekstu) oraz zapamiętywany jest wskaźnik (za pomocą
SetWindowLong).
Podczas przetwarzania komunikatu WM_COMMAND zmieniającego kolor tek-
y r
stu (jak pamiętam , komunikaty tego typu mają początek w procedurze okn"
ramowego) procedura HelloWndProc pozyskuje za pomocą GetWindowLong wskaź-
nik do bloku pamięci zawierający strukturę HELLODATA. Używając tej struktu-
ry, HelloWndProc likwiduje zaznaczenie opcji menu, zaznacza wybraną pozycję
menu i zapisuje nowy kolor.
Procedura okna dokumentu odbiera komunikat WM-MDIACTIVATE wtedy, gdy
okno staje się aktywne lub nieaktywne (co można rozpoznać po tym, czy lParam
zawiera uchwyt okna). Jak pamiętamy, program MDIDEMO ma trzy różne menu:
MdiMenuInit jest używane dla okna aplikacji pozbawionego okien dokumentów,
MdiMenuHello dla aktywnego okna Hello, a MdiMenuRect dla aktywnego okna
Rect.
Możliwość modyfikowania menu gwarantuje oknu dokumentu komunikat
WM MDIACTIVATE. Jeżeli IParam zawiera uchwyt okna (co oznacza, że okno
staje się aktywne), wówczas HelloWndProc zmienia menu na MdiMenuHello. Je-
żeli lParam zawiera uchwyt innego okna, HelloWndProc zmienia menu na Mdi-
MenuInit.
HelloWndProc dokonuje zmiany menu, wysyłając do okna-klienta komunikat
WM MDISETMENU. Okno-klient przetwarza ten komunikat, usuwając wykaz
dokumentów z bieżącego menu i dopisując go do nowego menu. W ten oto spo-
sób wykaz dokumentów jest przenoszony z menu MdiMenuInit (które jest włą-
czone w chwili utworzenia pierwszego dokumentu) do menu MdiMenuHello.
W aplikacjach MDI nie należy zmieniać menu za pomocą funkcji SetMenu.
Inny ciekawy patent związany jest ze znacznikami pojawiającymi się obok opcji
podmenu Kolor. Ustawienie znacznika obok tej, a nie innej opcji, stanowi infor-
mację związaną z konkretnym oknem dokumentu, powinno bowiem być możli-
we ustawienie czarnego koloru tekstu w jednym z okien i czerwonego w innym.
Znaczniki obok pozycji menu powinny ponadto wiernie odzwierciedlać opcję
wybraną dla danego okna. Z tego powodu HelloWndProc likwiduje zaznaczenie
w chwili, gdy okno staje się nieaktywne, i ponownie ustawia znacznik (obok wła-
ściwej pozycji) wtedy, gdy okno staje się aktywne.
Wartości wParam i IParam komunikatu WM_MDIACTIVATE to dwa uchwyty -
odpowiednio - okien dezaktywowanych i aktywowanych. Procedura okna uzy-
skuje pierwszy komunikat WM_MDIACTIVATE lParam ustawiony na uchwyt
okna. Kiedy zaś okno jest niszczone, otrzymuje ostatni komunikat lParam usta-
wiony na inną wartość. Jeżeli użytkownik przełącza się z jednego dokumentu na
inny, pierwsze okno dokumentu otrzymuje komunikat WM_MDIACTIVATE z pa-
rametrem IParam ustawionym na uchwyt pierwszego okna, przy którym proce-
dura okna ustawia menu na MdiMenuInit. Drugie okno dokumentu odbiera ko-
munikat WM MDIACTIVATE z parametrem lParam ustawionym na uchwyt
Rozdział 19: Interfejs wielodokumentowy 1063
drugiego okna, przy którym procedura okna ustawia menu odpowiednio na
MdiMenuHello lub MdiMenuRect. Jeżeli wszystkie okna są zamknięte, pozosta-
je menu MdiMenuInit.
Jako się rzekło, kiedy użytkownik wybierze z menu polecenie Zamknij albo Za-
mknij wśży'tkie; procedr" pr"meWndProc wysyła do okna potomnego komuni-
kat WMQUERYENDSESSION. HelloWndProc przetwarza komunikaty WMQU-
ERYENDSESSION i WM CLOSE, wyświetlając okno komunikatu i pytając użyt-
kownika, czy można je zamknąć (w rzeczywistym programie to okno może za-
wierać pytanie o korueczność zapisania pliku dokumentu). Jeżeli użytkownik
zadecyduje, że okno nie może zostać zamknięte, procedura okna zwróci 0.
I Podczas załatwiania komunikatu WM_DESTROY HelloWndProc zwalnia blok
pamięci zarezerwowany podczas obsługi komunikatu WM CREATE.
Wszystkie nieobsłużone komunikaty muszą zostać przekazane do DefMDIChild-
Proc (nie do Def4VindowProc) i poddane przetwarzaniu domyślnemu. Kilka ko-
munikatów należy przekazać do DefMDIChildProc niezależnie od tego, czy pro-
cedura okna potomnego ma z nimi coś wspólnego czy nie. Są to WMCHILDAC-
TIVATE, WMGETMINMAXINFO, WM MENUCHAR, WM MOVE, WM SET
FOCUS, WM SIZE i 4VMSYSCOMMAND.
Procedury IZectWndProc nie będę omawiał dokładniej, bo choć jest ona dość po-
dobna do HelloWndProc, zwłaszcza pod względem nadbudowy, jest od niej nieco
prostsza (nie ma mowy o żadnych opcjach menu, użytkownik nie jest pytany
o zgodę na zamknięcie okna). Warto jednak zauważyć, że ftectWndProc rozgałę-
zia się po obsłużeniu WMSIZE, aby komunikat mógł trafić DefMDIChildProc.
Sprzątanie
W procedurze WinMain programu MDIDEMO trzy menu zdefiniowane w skrypcie
zasobów są ładowane za pomocą LoadMenu. Zwykle Windows niszczy menu pod-
czas likwidowania okna, z którym jest ono skojarzone. Dotyczy to menu Init. Ist-
nieją jednak menu niepodłączone do żadnego okna, które powinny być niszczo-
ne jawnie. Z tego powodu w MDIDEMO znajdują się dwa wywołania Destroy-
Menu pod koniec procedury WinMain - za ich pomocą program pozbywa się menu
Hello i Rect.
Wyszukiwarka
Podobne podstrony:
Programowniae windows petzold Petzold01
Programowniae windows petzold Petzold05
Programowniae windows petzold Petzold08
Programowniae windows petzold Petzold09
Programowniae windows petzold Petzold13
Programowniae windows petzold Petzold24
Programowniae windows petzold Petzold02
Programowniae windows petzold Petzold21
Programowniae windows petzold Petzold22
Programowniae windows petzold Petzold14
Programowniae windows petzold Petzold04
Programowniae windows petzold Petzold03
Asembler Podstawy programowania w Windows
2 Podstawy programowania Windows (2)
Visual Studio 05 Programowanie z Windows API w jezyku C vs25pw
informatyka usb praktyczne programowanie z windows api w c andrzej daniluk ebook
więcej podobnych podstron