Uso dell'interfaccia a documenti multipli

In questa sezione viene illustrato come eseguire le attività seguenti:

Per illustrare queste attività, questa sezione include esempi di Multipad, una tipica applicazione MDI (Multiple-Document Interface).

Registrazione di classi di finestre figlio e frame

Un'applicazione MDI tipica deve registrare due classi finestra: una per la finestra cornice e una per le finestre figlio. Se un'applicazione supporta più di un tipo di documento ,ad esempio un foglio di calcolo e un grafico, deve registrare una classe finestra per ogni tipo.

La struttura della classe per la finestra cornice è simile alla struttura di classe per la finestra principale nelle applicazioni non MDI. La struttura di classi per le finestre figlio MDI è leggermente diversa dalla struttura per le finestre figlio nelle applicazioni non MDI, come indicato di seguito:

  • La struttura della classe deve avere un'icona, perché l'utente può ridurre al minimo una finestra figlio MDI come se fosse una normale finestra dell'applicazione.
  • Il nome del menu deve essere NULL, perché una finestra figlio MDI non può avere un proprio menu.
  • La struttura della classe deve riservare spazio aggiuntivo nella struttura della finestra. Con questo spazio, l'applicazione può associare dati, ad esempio un nome file, a una particolare finestra figlio.

Nell'esempio seguente viene illustrato come Multipad registra le classi cornice e finestra figlio.

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; 
} 

Creazione di finestre cornice e figlio

Dopo aver registrato le relative classi finestra, un'applicazione MDI può creare le relative finestre. Prima di tutto, crea la finestra cornice usando la funzione CreateWindow o CreateWindowEx . Dopo aver creato la finestra cornice, l'applicazione crea nuovamente la finestra client usando CreateWindow o CreateWindowEx. L'applicazione deve specificare MDICLIENT come nome della classe della finestra client; MDICLIENT è una classe finestra preregistrata definita dal sistema. Il parametro lpvParam di CreateWindow o CreateWindowEx deve puntare a una struttura CLIENTCREATESTRUCT. Questa struttura contiene i membri descritti nella tabella seguente:

Membro Descrizione
hWindowMenu Handle per il menu della finestra usato per controllare le finestre figlio MDI. Quando vengono create finestre figlio, l'applicazione aggiunge i titoli al menu della finestra come voci di menu. L'utente può quindi attivare una finestra figlio facendo clic sul relativo titolo nel menu della finestra.
idFirstChild Specifica l'identificatore della prima finestra figlio MDI. A questo identificatore viene assegnata la prima finestra figlio MDI creata. Vengono create finestre aggiuntive con identificatori di finestra incrementati. Quando una finestra figlio viene eliminata definitivamente, il sistema riassegna immediatamente gli identificatori di finestra per mantenere contiguo l'intervallo.

 

Quando il titolo di una finestra figlio viene aggiunto al menu della finestra, il sistema assegna un identificatore alla finestra figlio. Quando l'utente fa clic sul titolo di una finestra figlio, la finestra cornice riceve un messaggio WM_COMMAND con l'identificatore nel parametro wParam . È necessario specificare un valore per il membro idFirstChild che non è in conflitto con gli identificatori delle voci di menu nel menu della finestra cornice.

La procedura della finestra cornice del multipad crea la finestra del client MDI durante l'elaborazione del messaggio di WM_CREATE . Nell'esempio seguente viene illustrato come viene creata la finestra client.

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; 

I titoli delle finestre figlio vengono aggiunti nella parte inferiore del menu della finestra. Se l'applicazione aggiunge stringhe al menu della finestra tramite la funzione AppendMenu , queste stringhe possono essere sovrascritte dai titoli delle finestre figlio quando il menu della finestra viene ridiscritto (ogni volta che viene creata o eliminata definitivamente una finestra figlio). Un'applicazione MDI che aggiunge stringhe al relativo menu finestra deve usare la funzione InsertMenu e verificare che i titoli delle finestre figlio non abbiano sovrascritto queste nuove stringhe.

Usare lo stile WS_CLIPCHILDREN per creare la finestra del client MDI per impedire che la finestra venga disegnata sulle finestre figlio.

Scrittura del ciclo di messaggi principale

Il ciclo di messaggi principale di un'applicazione MDI è simile a quello di un'applicazione non MDI che gestisce i tasti di scelta rapida. La differenza è che il ciclo di messaggi MDI chiama la funzione TranslateMDISysAccel prima di controllare i tasti di scelta rapida definiti dall'applicazione o prima di inviare il messaggio.

Nell'esempio seguente viene illustrato il ciclo di messaggi di una tipica applicazione MDI. Si noti che GetMessage può restituire -1 se si verifica un errore.

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); 
        } 
    } 
}

La funzione TranslateMDISysAccel converte i messaggi WM_KEYDOWN in messaggi WM_SYSCOMMAND e li invia alla finestra figlio MDI attiva. Se il messaggio non è un messaggio di acceleratore MDI, la funzione restituisce FALSE, nel qual caso l'applicazione usa la funzione TranslateAccelerator per determinare se è stato premuto uno dei tasti di scelta rapida definiti dall'applicazione. In caso contrario, il ciclo invia il messaggio alla routine della finestra appropriata.

Scrittura della routine finestra cornice

La procedura della finestra per una finestra cornice MDI è simile a quella della finestra principale di un'applicazione non MDI. La differenza è che una routine della finestra cornice passa tutti i messaggi che non gestisce alla funzione DefFrameProc anziché alla funzione DefWindowProc . Inoltre, la routine della finestra cornice deve anche passare alcuni messaggi gestiti, inclusi quelli elencati nella tabella seguente.

Message Risposta
WM_COMMAND Attiva la finestra figlio MDI scelta dall'utente. Questo messaggio viene inviato quando l'utente sceglie una finestra figlio MDI dal menu della finestra cornice MDI. L'identificatore della finestra che accompagna questo messaggio identifica la finestra figlio MDI da attivare.
WM_MENUCHAR Apre il menu della finestra figlio MDI attiva quando l'utente preme la combinazione di tasti ALT+ (meno).
WM_SETFOCUS Passa lo stato attivo della tastiera alla finestra del client MDI, che a sua volta lo passa alla finestra figlio MDI attiva.
WM_SIZE Ridimensiona la finestra del client MDI in modo che si adatti all'area client della nuova finestra cornice. Se la routine della finestra cornice ridimensiona la finestra client MDI a una dimensione diversa, non deve passare il messaggio alla funzione DefWindowProc .

 

La procedura della finestra cornice in Multipad è denominata MPFrameWndProc. La gestione di altri messaggi da MPFrameWndProc è simile a quella delle applicazioni non MDI. WM_COMMAND messaggi in Multipad vengono gestiti dalla funzione CommandHandler definita localmente. Per i messaggi di comando Multipad non gestisce, CommandHandler chiama la funzione DefFrameProc . Se Multipad non usa DefFrameProc per impostazione predefinita, l'utente non può attivare una finestra figlio dal menu della finestra, perché il messaggio di WM_COMMAND inviato facendo clic sulla voce di menu della finestra andrebbe persa.

Scrittura della routine finestra figlio

Analogamente alla procedura della finestra cornice, per impostazione predefinita, una routine finestra figlio MDI usa una funzione speciale per l'elaborazione dei messaggi. Tutti i messaggi non gestiti dalla routine della finestra figlio devono essere passati alla funzione DefMDIChildProc anziché alla funzione DefWindowProc . Inoltre, alcuni messaggi di gestione delle finestre devono essere passati a DefMDIChildProc, anche se l'applicazione gestisce il messaggio, affinché MDI funzioni correttamente. Di seguito sono riportati i messaggi che l'applicazione deve passare a DefMDIChildProc.

Message Risposta
WM_CHILDACTIVATE Esegue l'elaborazione dell'attivazione quando le finestre figlio MDI vengono ridimensionate, spostate o visualizzate. Questo messaggio deve essere passato.
WM_GETMINMAXINFO Calcola le dimensioni di una finestra figlio MDI ingrandita, in base alle dimensioni correnti della finestra del client MDI.
WM_MENUCHAR Passa il messaggio alla finestra cornice MDI.
WM_MOVE Ricalcola le barre di scorrimento del client MDI, se presenti.
WM_SETFOCUS Attiva la finestra figlio, se non è la finestra figlio MDI attiva.
WM_SIZE Esegue operazioni necessarie per modificare le dimensioni di una finestra, in particolare per ottimizzare o ripristinare una finestra figlio MDI. Se non si passa questo messaggio alla funzione DefMDIChildProc , si ottengono risultati estremamente indesiderati.
WM_SYSCOMMAND Gestisce i comandi di menu della finestra (in precedenza noti come system): SC_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZE e SC_MAXIMIZE.

 

Creazione di una finestra figlio

Per creare una finestra figlio MDI, un'applicazione può chiamare la funzione CreateMDIWindow o inviare un messaggio di WM_MDICREATE alla finestra del client MDI. L'applicazione può usare la funzione CreateWindowEx con lo stile WS_EX_MDICHILD per creare finestre figlio MDI. Un'applicazione MDI a thread singolo può usare un metodo per creare una finestra figlio. Un thread in un'applicazione MDI multithreaded deve usare la funzione CreateMDIWindow o CreateWindowEx per creare una finestra figlio in un thread diverso.

Il parametro lParam di un messaggio di WM_MDICREATE è un puntatore a una struttura MDICREATESTRUCT . La struttura include quattro membri della dimensione: x e y, che indicano le posizioni orizzontali e verticali della finestra e cx e cy, che indicano le estensioni orizzontali e verticali della finestra. Uno di questi membri può essere assegnato in modo esplicito dall'applicazione oppure può essere impostato su CW_USEDEFAULT, nel qual caso il sistema seleziona una posizione, una dimensione o entrambe, in base a un algoritmo a catena. In ogni caso, tutti e quattro i membri devono essere inizializzati. Multipad usa CW_USEDEFAULT per tutte le dimensioni.

L'ultimo membro della struttura MDICREATESTRUCT è il membro dello stile, che può contenere bit di stile per la finestra. Per creare una finestra figlio MDI che può avere qualsiasi combinazione di stili di finestra, specificare lo stile della finestra MDIS_ALLCHILDSTYLES . Quando questo stile non è specificato, una finestra figlio MDI ha la WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLL e gli stili WS_VSCROLL come impostazioni predefinite.

Multipad crea le finestre figlio MDI usando la relativa funzione AddFile definita in locale ,disponibile nel file di origine MPFILE. C). La funzione AddFile imposta il titolo della finestra figlio assegnando il membro szTitle della struttura MDICREATESTRUCT della finestra al nome del file modificato o a "Senza titolo". Il membro szClass è impostato sul nome della classe di finestra figlio MDI registrata nella funzione InitializeApplication di Multipad. Il membro hOwner è impostato sull'handle dell'istanza dell'applicazione.

Nell'esempio seguente viene illustrata la funzione AddFile in 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; 
} 

Il puntatore passato nel parametro lParam del messaggio di WM_MDICREATE viene passato alla funzione CreateWindow e viene visualizzato come primo membro della struttura CREATESTRUCT , passato nel messaggio WM_CREATE . In Multipad la finestra figlio inizializza se stessa durante l'elaborazione di messaggi WM_CREATE inizializzando le variabili del documento nei dati aggiuntivi e creando la finestra figlio del controllo di modifica.