Udostępnij przez


Korzystanie z interfejsu wielu dokumentów

W tej sekcji opisano sposób wykonywania następujących zadań:

Aby zilustrować te zadania, ta sekcja zawiera przykłady z multipad, typowej aplikacji interfejsu wielu dokumentów (MDI).

Rejestrowanie klas okien podrzędnych i ramowych

Typowa aplikacja MDI musi zarejestrować dwie klasy okien: jedną dla okna ramowego i jedną dla okien podrzędnych. Jeśli aplikacja obsługuje więcej niż jeden typ dokumentu (na przykład arkusz kalkulacyjny i wykres), musi zarejestrować klasę okna dla każdego typu.

Struktura klasy okna ramowego jest podobna do struktury klas dla głównego okna w aplikacjach innych niż MDI. Struktura klas dla okien podrzędnych MDI różni się nieco od struktury okien podrzędnych w aplikacjach innych niż MDI w następujący sposób:

  • Struktura klas powinna mieć ikonę, ponieważ użytkownik może zminimalizować okno podrzędne MDI tak, jakby było to normalne okno aplikacji.
  • Nazwa menu powinna mieć wartość null, ponieważ okno podrzędne MDI nie może mieć własnego menu.
  • Struktura klasy powinna zarezerwować dodatkowe miejsce w strukturze okna. W tym miejscu aplikacja może skojarzyć dane, takie jak nazwa pliku, z określonym oknem podrzędnym.

W poniższym przykładzie pokazano, jak Multipad rejestruje klasy okien głównych i podrzędnych.

BOOL WINAPI InitializeApplication() 
{ 
    WNDCLASS wc; 
 
    // Register the frame window class. 
 
    wc.style         = 0; 
    wc.lpfnWndProc   = (WNDPROC) MPFrameWndProc; 
    wc.cbClsExtra    = 0; 
    wc.cbWndExtra    = 0; 
    wc.hInstance     = hInst; 
    wc.hIcon         = LoadIcon(hInst, IDMULTIPAD); 
    wc.hCursor       = LoadCursor((HANDLE) NULL, IDC_ARROW); 
    wc.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE + 1); 
    wc.lpszMenuName  = IDMULTIPAD; 
    wc.lpszClassName = szFrame; 
 
    if (!RegisterClass (&wc) ) 
        return FALSE; 
 
    // Register the MDI child window class. 
 
    wc.lpfnWndProc   = (WNDPROC) MPMDIChildWndProc; 
    wc.hIcon         = LoadIcon(hInst, IDNOTE); 
    wc.lpszMenuName  = (LPCTSTR) NULL; 
    wc.cbWndExtra    = CBWNDEXTRA; 
    wc.lpszClassName = szChild; 
 
    if (!RegisterClass(&wc)) 
        return FALSE; 
 
    return TRUE; 
} 

Tworzenie ramek i okienek podrzędnych

Po zarejestrowaniu klas okien aplikacja MDI może utworzyć swoje okna. Najpierw tworzy swoje okno ramki przy użyciu funkcji CreateWindow lub CreateWindowEx. Po utworzeniu okna ramki aplikacja ponownie utworzy okno klienta przy użyciu CreateWindow lub CreateWindowEx. Aplikacja powinna określić mdICLIENT jako nazwę klasy okna klienta; MDICLIENT jest wstępnie wyrejestrowaną klasą okien zdefiniowaną przez system. Parametr lpvParamCreateWindow lub CreateWindowEx powinien wskazywać na strukturę CLIENTCREATESTRUCT. Ta struktura zawiera członków opisanych w poniższej tabeli:

Członek Opis
hWindowMenu Dojście do menu okna używanego do kontrolowania okien podrzędnych MDI. Gdy tworzone są okna podrzędne, aplikacja dodaje ich tytuły do menu okna jako elementy menu. Użytkownik może następnie aktywować okno podrzędne, klikając jego tytuł w menu okna.
idFirstChild Określa identyfikator pierwszego okna podrzędnego MDI. Do pierwszego utworzonego okna podrzędnego MDI jest przypisany ten identyfikator. Dodatkowe okna są tworzone przy użyciu identyfikatorów okien przyrostowych. Gdy okno podrzędne zostanie zniszczone, system natychmiast ponownie przypisze identyfikatory okien, aby zachować ciągły zakres.

 

Po dodaniu tytułu okna pomocniczego do menu okna, system przypisuje identyfikator do okna pomocniczego. Gdy użytkownik kliknie tytuł okna podrzędnego, okno ramki otrzyma komunikat WM_COMMAND z identyfikatorem w parametrze wParam. Należy określić wartość członka idFirstChild, która nie powoduje konfliktu z identyfikatorami elementów menu w menu w oknie ramki.

Procedura ramowego okna Multipad tworzy okno klienta MDI podczas przetwarzania komunikatu WM_CREATE. W poniższym przykładzie pokazano, jak jest tworzone okno klienta.

case WM_CREATE: 
    { 
        CLIENTCREATESTRUCT ccs; 
 
        // Retrieve the handle to the window menu and assign the 
        // first child window identifier. 
 
        ccs.hWindowMenu = GetSubMenu(GetMenu(hwnd), WINDOWMENU); 
        ccs.idFirstChild = IDM_WINDOWCHILD; 
 
        // Create the MDI client window. 
 
        hwndMDIClient = CreateWindow( "MDICLIENT", (LPCTSTR) NULL, 
            WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL, 
            0, 0, 0, 0, hwnd, (HMENU) 0xCAC, hInst, (LPSTR) &ccs); 
 
        ShowWindow(hwndMDIClient, SW_SHOW); 
    } 
    break; 

Tytuły okien podrzędnych są dodawane na dole menu okna. Jeśli aplikacja dodaje ciągi znaków do menu okna przy użyciu funkcji AppendMenu, te napisy mogą być zastąpione przez tytuły okien podrzędnych, gdy menu okna zostanie przemalowane (za każdym razem, gdy zostanie utworzone lub zniszczone okno podrzędne). Aplikacja MDI, która dodaje ciągi do menu okna, powinna używać funkcji InsertMenu i sprawdzić, czy tytuły okien podrzędnych nie zastąpiły tych nowych ciągów.

Użyj stylu WS_CLIPCHILDREN, aby utworzyć okno klienta MDI i zapobiec przysłanianiu okien podrzędnych przez okno główne.

Pisanie pętli komunikatów głównych

Główna pętla komunikatów aplikacji MDI jest podobna do pętli komunikatów aplikacji nie-MDI obsługujących klawisze akceleratora. Różnica polega na tym, że pętla komunikatów MDI wywołuje funkcję TranslateMDISysAccel przed sprawdzeniem kluczy akceleratorów zdefiniowanych przez aplikację lub przed wysłaniem komunikatu.

W poniższym przykładzie przedstawiono pętlę komunikatów typowej aplikacji MDI. Należy pamiętać, że GetMessage może zwrócić -1, jeśli wystąpi błąd.

MSG msg;
BOOL bRet;

while ((bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0)
{
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else 
    { 
        if (!TranslateMDISysAccel(hwndMDIClient, &msg) && 
                !TranslateAccelerator(hwndFrame, hAccel, &msg))
        { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 
}

Funkcja TranslateMDISysAccel tłumaczy komunikaty WM_KEYDOWN na komunikaty WM_SYSCOMMAND i wysyła je do aktywnego okna podrzędnego MDI. Jeśli komunikat nie jest komunikatem akceleratora MDI, funkcja zwraca FALSE, w takim przypadku aplikacja używa funkcji TranslateAccelerator w celu określenia, czy którykolwiek z klawiszy skrótów zdefiniowanych przez aplikację został naciśnięty. Jeśli tak nie jest, pętla wysyła komunikat do odpowiedniej procedury okna.

Pisanie procedury okna ramowego

Procedura okna dla okna głównego MDI jest podobna do okna głównego aplikacji nie korzystającej z MDI. Różnica polega na tym, że procedura okna ramowego przekazuje wszystkie komunikaty, których nie obsługuje, do funkcji DefFrameProc, a nie do funkcji DefWindowProc. Ponadto procedura okna ramowego musi również przekazać niektóre komunikaty, które obsługuje, w tym te wymienione w poniższej tabeli.

Komunikat Odpowiedź
WM_COMMAND Aktywuje okno podrzędne MDI wybrane przez użytkownika. Ten komunikat jest wysyłany, gdy użytkownik wybierze okno podrzędne MDI z menu okna okna ramki MDI. Identyfikator okna towarzyszący tej wiadomości identyfikuje okno podrzędne MDI, które ma zostać aktywowane.
WM_MENUCHAR Otwiera menu okna aktywnego okna podrzędnego MDI, gdy użytkownik naciska kombinację ALT+ – (minus).
WM_SETFOCUS Przekazuje fokus klawiatury do okna klienta MDI, które z kolei przekazuje je do aktywnego okna podrzędnego MDI.
WM_SIZE Zmienia rozmiar okna klienta MDI tak, aby mieściło się w obszarze klienta nowego okna ramowego. Jeśli procedura okna ramowego zmieni rozmiar okna klienta MDI, nie powinna przekazać komunikatu do funkcji DefWindowProc.

 

Procedura okna ramowego w multipad jest nazywana MPFrameWndProc. Obsługa innych komunikatów przez mpFrameWndProc jest podobna do obsługi aplikacji innych niż MDI. WM_COMMAND komunikaty w Multipad są obsługiwane przez lokalnie zdefiniowaną funkcję CommandHandler. W przypadku komunikatów, które Multipad nie obsługuje, funkcja CommandHandler wywołuje DefFrameProc. Jeśli multipad nie używa DefFrameProc domyślnie, użytkownik nie może aktywować okna podrzędnego z menu okna, ponieważ komunikat WM_COMMAND wysłany przez kliknięcie elementu menu okna zostanie utracony.

Pisanie procedury okna podrzędnego

Podobnie jak procedura okna ramowego, procedura okna podrzędnego MDI domyślnie używa specjalnej funkcji do przetwarzania komunikatów. Wszystkie komunikaty, których procedura okna podrzędnego nie obsługuje, muszą zostać przekazane do funkcji DefMDIChildProc, a nie do funkcji DefWindowProc. Ponadto niektóre komunikaty zarządzania oknami muszą być przekazywane do DefMDIChildProc, nawet jeśli aplikacja obsługuje komunikat, aby mdI działał poprawnie. Poniżej przedstawiono komunikaty, które aplikacja musi przekazać do DefMDIChildProc.

Komunikat Odpowiedź
WM_CHILDACTIVATE Wykonuje proces aktywacji, gdy okna potomne MDI są skalowane, przenoszone lub wyświetlane. Ten komunikat musi zostać przekazany.
WM_GETMINMAXINFO Oblicza rozmiar zmaksymalizowanego okna podrzędnego MDI na podstawie bieżącego rozmiaru okna klienta MDI.
WM_MENUCHAR Przekazuje komunikat do okna ramki MDI.
WM_MOVE Oblicza ponownie paski przewijania klienta MDI, jeśli są obecne.
WM_SETFOCUS Aktywuje okno podrzędne, jeśli nie jest ono aktualnie aktywnym oknem podrzędnym MDI.
WM_SIZE Wykonuje operacje niezbędne do zmiany rozmiaru okna, szczególnie w celu maksymalizacji lub przywrócenia okna podrzędnego MDI. Niezdołanie przekazać tego komunikatu do funkcji DefMDIChildProc prowadzi do wysoce niepożądanych wyników.
WM_SYSCOMMAND Obsługuje polecenia menu okna (wcześniej znanego jako menu systemowe): SC_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZEi SC_MAXIMIZE.

 

Tworzenie okna podrzędnego

Aby utworzyć okno podrzędne MDI, aplikacja może wywołać funkcję CreateMDIWindow lub wysłać komunikat WM_MDICREATE do okna klienta MDI. (Aplikacja może używać funkcji CreateWindowEx ze stylem WS_EX_MDICHILD do tworzenia okien podrzędnych MDI). Jednowątkowa aplikacja MDI może użyć jednej metody do utworzenia okna podrzędnego. Wątek w wielowątkowej aplikacji MDI musi używać funkcji CreateMDIWindow lub CreateWindowEx, aby utworzyć okno podrzędne w innym wątku.

Parametr lParam komunikatu WM_MDICREATE jest dalekim wskaźnikiem do struktury MDICREATESTRUCT. Struktura zawiera cztery elementy członkowskie wymiarów: x i y, które wskazują położenie w poziomie i w pionie okna, oraz cx i cy, które wskazują zakresy poziome i pionowe okna. Każdy z tych składników można przypisać jawnie przez aplikację lub ustawić na CW_USEDEFAULT, w takim przypadku system wybiera pozycję, rozmiar lub oba te elementy, zgodnie z algorytmem kaskadowym. W każdym przypadku wszystkie cztery elementy muszą być zainicjowane. Multipad używa CW_USEDEFAULT dla wszystkich wymiarów.

Ostatnim członkiem struktury MDICREATESTRUCT jest członek stylu , który może zawierać bity stylu dla okna. Aby utworzyć okno podrzędne MDI, które może mieć dowolną kombinację stylów okna, określ styl okna MDIS_ALLCHILDSTYLES. Jeśli ten styl nie zostanie określony, okno podrzędne MDI ma WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLLi style WS_VSCROLL jako ustawienia domyślne.

Multipad tworzy okna podrzędne MDI przy użyciu lokalnie zdefiniowanej funkcji AddFile (znajdującej się w pliku źródłowym MPFILE. C). Funkcja AddFile ustawia tytuł okna podrzędnego przez przypisanie elementu członkowskiego szTitle elementu członkowskiego MDICREATESTRUCT okna na nazwę edytowanego pliku lub na "Bez tytułu". Składowa szClass jest ustawiona na nazwę klasy okna podrzędnego MDI zarejestrowanej w funkcji InitializeApplication multipad. Element hOwner jest ustawiony na uchwyt wystąpienia aplikacji.

W poniższym przykładzie przedstawiono funkcję AddFile w systemie Multipad.

HWND APIENTRY AddFile(pName) 
TCHAR * pName; 
{ 
    HWND hwnd; 
    TCHAR sz[160]; 
    MDICREATESTRUCT mcs; 
 
    if (!pName) 
    { 
 
        // If the pName parameter is NULL, load the "Untitled" 
        // string from the STRINGTABLE resource and set the szTitle 
        // member of MDICREATESTRUCT. 
 
        LoadString(hInst, IDS_UNTITLED, sz, sizeof(sz)/sizeof(TCHAR)); 
        mcs.szTitle = (LPCTSTR) sz; 
    } 
    else 
 
        // Title the window with the full path and filename, 
        // obtained by calling the OpenFile function with the 
        // OF_PARSE flag, which is called before AddFile(). 
 
        mcs.szTitle = of.szPathName; 
 
    mcs.szClass = szChild; 
    mcs.hOwner  = hInst; 
 
    // Use the default size for the child window. 
 
    mcs.x = mcs.cx = CW_USEDEFAULT; 
    mcs.y = mcs.cy = CW_USEDEFAULT; 
 
    // Give the child window the default style. The styleDefault 
    // variable is defined in MULTIPAD.C. 
 
    mcs.style = styleDefault; 
 
    // Tell the MDI client window to create the child window. 
 
    hwnd = (HWND) SendMessage (hwndMDIClient, WM_MDICREATE, 0, 
        (LONG) (LPMDICREATESTRUCT) &mcs); 
 
    // If the file is found, read its contents into the child 
    // window's client area. 
 
    if (pName) 
    { 
        if (!LoadFile(hwnd, pName)) 
        { 
 
            // Cannot load the file; close the window. 
 
            SendMessage(hwndMDIClient, WM_MDIDESTROY, 
                (DWORD) hwnd, 0L); 
        } 
    } 
    return hwnd; 
} 

Wskaźnik przekazany w parametrze lParam komunikatu WM_MDICREATE jest przekazywany do funkcji CreateWindow i pojawia się jako pierwszy członek struktury CREATESTRUCT, przekazany w komunikacie WM_CREATE. W aplikacji Multipad okno podrzędne inicjuje się podczas przetwarzania komunikatu WM_CREATE, inicjując zmienne dokumentu w dodatkowych danych i tworząc okno podrzędne kontrolki edytora.