Share via


Utilisation de l’interface de documents multiples

Cette section explique comment effectuer les tâches suivantes :

Pour illustrer ces tâches, cette section inclut des exemples de Multipad, une application MDI (Multipad) classique.

Inscription de classes de fenêtres enfants et frame

Une application MDI classique doit inscrire deux classes de fenêtre : l’une pour sa fenêtre frame et l’autre pour ses fenêtres enfants. Si une application prend en charge plusieurs types de document (par exemple, une feuille de calcul et un graphique), elle doit inscrire une classe de fenêtre pour chaque type.

La structure de classe de la fenêtre frame est similaire à la structure de classe de la fenêtre main dans les applications non MDI. La structure de classe pour les fenêtres enfants MDI diffère légèrement de la structure des fenêtres enfants dans les applications non MDI comme suit :

  • La structure de classe doit avoir une icône, car l’utilisateur peut réduire une fenêtre enfant MDI comme s’il s’agissait d’une fenêtre d’application normale.
  • Le nom du menu doit être NULL, car une fenêtre enfant MDI ne peut pas avoir son propre menu.
  • La structure de classe doit réserver de l’espace supplémentaire dans la structure de fenêtre. Avec cet espace, l’application peut associer des données, telles qu’un nom de fichier, à une fenêtre enfant particulière.

L’exemple suivant montre comment Multipad inscrit ses classes frame et de fenêtre enfant.

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

Création de fenêtres frame et enfants

Après avoir inscrit ses classes de fenêtre, une application MDI peut créer ses fenêtres. Tout d’abord, il crée sa fenêtre frame à l’aide de la fonction CreateWindow ou CreateWindowEx . Après avoir créé sa fenêtre frame, l’application crée sa fenêtre cliente, à nouveau à l’aide de CreateWindow ou CreateWindowEx. L’application doit spécifier MDICLIENT comme nom de classe de la fenêtre cliente ; MDICLIENT est une classe de fenêtre préinscriée définie par le système. Le paramètre lpvParam de CreateWindow ou CreateWindowEx doit pointer vers une structure CLIENTCREATESTRUCT . Cette structure contient les membres décrits dans le tableau suivant :

Membre Description
hWindowMenu Gérez le menu de la fenêtre utilisé pour contrôler les fenêtres enfants MDI. À mesure que des fenêtres enfants sont créées, l’application ajoute ses titres au menu de la fenêtre en tant qu’éléments de menu. L’utilisateur peut ensuite activer une fenêtre enfant en cliquant sur son titre dans le menu de la fenêtre.
idFirstChild Spécifie l’identificateur de la première fenêtre enfant MDI. Cet identificateur est attribué à la première fenêtre enfant MDI créée. Des fenêtres supplémentaires sont créées avec des identificateurs de fenêtre incrémentés. Lorsqu’une fenêtre enfant est détruite, le système réattribue immédiatement les identificateurs de fenêtre pour que leur plage reste contiguë.

 

Quand le titre d’une fenêtre enfant est ajouté au menu de la fenêtre, le système affecte un identificateur à la fenêtre enfant. Lorsque l’utilisateur clique sur le titre d’une fenêtre enfant, la fenêtre frame reçoit un message WM_COMMAND avec l’identificateur dans le paramètre wParam . Vous devez spécifier une valeur pour le membre idFirstChild qui n’est pas en conflit avec les identificateurs d’élément de menu dans le menu de la fenêtre frame.

La procédure de fenêtre frame de Multipad crée la fenêtre cliente MDI lors du traitement du message WM_CREATE . L’exemple suivant montre comment la fenêtre cliente est créée.

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; 

Les titres des fenêtres enfants sont ajoutés au bas du menu de la fenêtre. Si l’application ajoute des chaînes au menu de la fenêtre à l’aide de la fonction AppendMenu , ces chaînes peuvent être remplacées par les titres des fenêtres enfants lorsque le menu de la fenêtre est repeint (chaque fois qu’une fenêtre enfant est créée ou détruite). Une application MDI qui ajoute des chaînes à son menu de fenêtre doit utiliser la fonction InsertMenu et vérifier que les titres des fenêtres enfants n’ont pas remplacé ces nouvelles chaînes.

Utilisez le style WS_CLIPCHILDREN pour créer la fenêtre cliente MDI afin d’empêcher la fenêtre de peindre sur ses fenêtres enfants.

Écriture de la boucle de message principale

La boucle de message main d’une application MDI est similaire à celle d’une application non MDI qui gère les touches d’accélérateur. La différence est que la boucle de message MDI appelle la fonction TranslateMDISysAccel avant de vérifier les clés d’accélérateur définies par l’application ou avant de distribuer le message.

L’exemple suivant montre la boucle de message d’une application MDI classique. Notez que GetMessage peut retourner -1 en cas d’erreur.

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 fonction TranslateMDISysAccel traduit WM_KEYDOWN messages en messages WM_SYSCOMMAND et les envoie à la fenêtre enfant MDI active. Si le message n’est pas un message d’accélérateur MDI, la fonction retourne FALSE, auquel cas l’application utilise la fonction TranslateAccelerator pour déterminer si l’une des touches d’accélérateur définies par l’application a été enfoncée. Si ce n’est pas le cas, la boucle distribue le message à la procédure de fenêtre appropriée.

Écriture de la procédure de fenêtre Frame

La procédure de fenêtre pour une fenêtre frame MDI est similaire à celle de la fenêtre main d’une application non MDI. La différence est qu’une procédure de fenêtre frame transmet tous les messages qu’elle ne gère pas à la fonction DefFrameProc plutôt qu’à la fonction DefWindowProc . En outre, la procédure de fenêtre frame doit également passer certains messages qu’elle gère, y compris ceux répertoriés dans le tableau suivant.

Message response
WM_COMMAND Active la fenêtre enfant MDI que l’utilisateur choisit. Ce message est envoyé lorsque l’utilisateur choisit une fenêtre enfant MDI dans le menu de la fenêtre cadre MDI. L’identificateur de fenêtre qui accompagne ce message identifie la fenêtre enfant MDI à activer.
WM_MENUCHAR Ouvre le menu de la fenêtre enfant MDI active lorsque l’utilisateur appuie sur la combinaison de touches ALT+ – (moins).
WM_SETFOCUS Transmet le focus clavier à la fenêtre cliente MDI, qui à son tour le transmet à la fenêtre enfant MDI active.
WM_SIZE Redimensionne la fenêtre cliente MDI pour qu’elle s’adapte à la zone cliente de la nouvelle fenêtre frame. Si la procédure de fenêtre frame dimensionne la fenêtre cliente MDI à une autre taille, elle ne doit pas transmettre le message à la fonction DefWindowProc .

 

La procédure de fenêtre frame dans Multipad est appelée MPFrameWndProc. La gestion d’autres messages par MPFrameWndProc est similaire à celle des applications non MDI. WM_COMMAND messages dans Multipad sont gérés par la fonction CommandHandler définie localement. Pour les messages de commande que Multipad ne gère pas, CommandHandler appelle la fonction DefFrameProc . Si Multipad n’utilise pas DefFrameProc par défaut, l’utilisateur ne peut pas activer une fenêtre enfant à partir du menu de la fenêtre, car le message WM_COMMAND envoyé en cliquant sur l’élément de menu de la fenêtre est perdu.

Écriture de la procédure de fenêtre enfant

Comme la procédure de fenêtre frame, une procédure de fenêtre enfant MDI utilise une fonction spéciale pour traiter les messages par défaut. Tous les messages que la procédure de fenêtre enfant ne gère pas doivent être passés à la fonction DefMDIChildProc plutôt qu’à la fonction DefWindowProc . En outre, certains messages de gestion des fenêtres doivent être passés à DefMDIChildProc, même si l’application gère le message, afin que MDI fonctionne correctement. Voici les messages que l’application doit transmettre à DefMDIChildProc.

Message response
WM_CHILDACTIVATE Effectue un traitement d’activation lorsque les fenêtres enfants MDI sont dimensionnées, déplacées ou affichées. Ce message doit être passé.
WM_GETMINMAXINFO Calcule la taille d’une fenêtre enfant MDI agrandie, en fonction de la taille actuelle de la fenêtre cliente MDI.
WM_MENUCHAR Transmet le message à la fenêtre de cadre MDI.
WM_MOVE Recalcule les barres de défilement du client MDI, si elles sont présentes.
WM_SETFOCUS Active la fenêtre enfant, si elle n’est pas la fenêtre enfant MDI active.
WM_SIZE Effectue les opérations nécessaires pour modifier la taille d’une fenêtre, en particulier pour optimiser ou restaurer une fenêtre enfant MDI. Le fait de ne pas transmettre ce message à la fonction DefMDIChildProc produit des résultats très indésirables.
WM_SYSCOMMAND Gère les commandes de menu de fenêtre (anciennement système) : SC_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZE et SC_MAXIMIZE.

 

Création d’une fenêtre enfant

Pour créer une fenêtre enfant MDI, une application peut appeler la fonction CreateMDIWindow ou envoyer un message WM_MDICREATE à la fenêtre du client MDI. (L’application peut utiliser la fonction CreateWindowEx avec le style WS_EX_MDICHILD pour créer des fenêtres enfants MDI.) Une application MDI à thread unique peut utiliser l’une ou l’autre méthode pour créer une fenêtre enfant. Un thread dans une application MDI multithread doit utiliser la fonction CreateMDIWindow ou CreateWindowEx pour créer une fenêtre enfant dans un autre thread.

Le paramètre lParam d’un message WM_MDICREATE est un pointeur lointain vers une structure MDICREATESTRUCT . La structure comprend quatre membres de dimension : x et y, qui indiquent les positions horizontales et verticales de la fenêtre, et cx et cy, qui indiquent les étendues horizontales et verticales de la fenêtre. L’un de ces membres peut être affecté explicitement par l’application, ou il peut être défini sur CW_USEDEFAULT, auquel cas le système sélectionne une position, une taille ou les deux, en fonction d’un algorithme en cascade. Dans tous les cas, les quatre membres doivent être initialisés. Multipad utilise CW_USEDEFAULT pour toutes les dimensions.

Le dernier membre de la structure MDICREATESTRUCT est le membre de style , qui peut contenir des bits de style pour la fenêtre. Pour créer une fenêtre enfant MDI qui peut avoir n’importe quelle combinaison de styles de fenêtre, spécifiez le style de fenêtre MDIS_ALLCHILDSTYLES . Lorsque ce style n’est pas spécifié, une fenêtre enfant MDI a les styles WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLL et WS_VSCROLL comme paramètres par défaut.

Multipad crée ses fenêtres enfants MDI à l’aide de sa fonction AddFile définie localement (située dans le fichier source MPFILE. C). La fonction AddFile définit le titre de la fenêtre enfant en affectant le membre szTitle de la structure MDICREATESTRUCT de la fenêtre au nom du fichier en cours de modification ou à « Sans titre ». Le membre szClass est défini sur le nom de la classe de fenêtre enfant MDI inscrite dans la fonction InitializeApplication de Multipad. Le membre hOwner est défini sur le handle instance de l’application.

L’exemple suivant montre la fonction AddFile dans 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; 
} 

Le pointeur transmis dans le paramètre lParam du message WM_MDICREATE est passé à la fonction CreateWindow et apparaît comme le premier membre de la structure CREATESTRUCT , transmis dans le message WM_CREATE . Dans Multipad, la fenêtre enfant s’initialise pendant WM_CREATE traitement des messages en initialisant des variables de document dans ses données supplémentaires et en créant la fenêtre enfant du contrôle d’édition.