Uso de la interfaz de varios documentos

En esta sección se explica cómo realizar las tareas siguientes:

Para ilustrar estas tareas, esta sección incluye ejemplos de Multipad, una aplicación típica de interfaz de varios documentos (MDI).

Registrar clases secundarias y de ventana de marco

Una aplicación MDI típica debe registrar dos clases de ventana: una para su ventana de marco y otra para sus ventanas secundarias. Si una aplicación admite más de un tipo de documento (por ejemplo, una hoja de cálculo y un gráfico), debe registrar una clase de ventana para cada tipo.

La estructura de clase de la ventana de marco es similar a la estructura de clases de la ventana principal en aplicaciones que no son MDI. La estructura de clase para las ventanas secundarias MDI difiere ligeramente de la estructura de las ventanas secundarias en aplicaciones que no son MDI de la siguiente manera:

  • La estructura de clases debe tener un icono, ya que el usuario puede minimizar una ventana secundaria MDI como si fuera una ventana de aplicación normal.
  • El nombre del menú debe ser NULL, porque una ventana secundaria MDI no puede tener su propio menú.
  • La estructura de clase debe reservar espacio adicional en la estructura de la ventana. Con este espacio, la aplicación puede asociar datos, como un nombre de archivo, con una ventana secundaria determinada.

En el ejemplo siguiente se muestra cómo Multipad registra sus clases de ventana secundaria y marco.

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

Crear marco y ventanas secundarias

Después de registrar sus clases de ventana, una aplicación MDI puede crear sus ventanas. En primer lugar, crea su ventana de marco mediante la función CreateWindow o CreateWindowEx . Después de crear su ventana de marco, la aplicación crea su ventana de cliente, de nuevo mediante CreateWindow o CreateWindowEx. La aplicación debe especificar MDICLIENT como nombre de clase de la ventana del cliente; MDICLIENT es una clase de ventana registrada previamente definida por el sistema. El parámetro lpvParam de CreateWindow o CreateWindowEx debe apuntar a una estructura CLIENTCREATESTRUCT . Esta estructura contiene los miembros descritos en la tabla siguiente:

Miembro Descripción
hWindowMenu Identificador del menú de ventana que se usa para controlar las ventanas secundarias de MDI. A medida que se crean ventanas secundarias, la aplicación agrega sus títulos al menú de la ventana como elementos de menú. Después, el usuario puede activar una ventana secundaria haciendo clic en su título en el menú de la ventana.
idFirstChild Especifica el identificador de la primera ventana secundaria MDI. A la primera ventana secundaria de MDI creada se le asigna este identificador. Se crean ventanas adicionales con identificadores de ventana incrementados. Cuando se destruye una ventana secundaria, el sistema reasigna inmediatamente los identificadores de ventana para mantener su intervalo contiguo.

 

Cuando se agrega el título de una ventana secundaria al menú de la ventana, el sistema asigna un identificador a la ventana secundaria. Cuando el usuario hace clic en el título de una ventana secundaria, la ventana de marco recibe un mensaje de WM_COMMAND con el identificador en el parámetro wParam . Debe especificar un valor para el miembro idFirstChild que no entre en conflicto con los identificadores de elemento de menú en el menú de la ventana de marco.

El procedimiento de ventana de marco de Multipad crea la ventana del cliente MDI al procesar el mensaje WM_CREATE . En el ejemplo siguiente se muestra cómo se crea la ventana del cliente.

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; 

Los títulos de las ventanas secundarias se agregan a la parte inferior del menú de la ventana. Si la aplicación agrega cadenas al menú de ventana mediante la función AppendMenu , estos títulos se pueden sobrescribir mediante los títulos de las ventanas secundarias cuando se vuelve a dibujar el menú de la ventana (siempre que se crea o destruye una ventana secundaria). Una aplicación MDI que agrega cadenas a su menú de ventana debe usar la función InsertMenu y comprobar que los títulos de las ventanas secundarias no han sobrescrito estas nuevas cadenas.

Use el estilo WS_CLIPCHILDREN para crear la ventana del cliente MDI para evitar que la ventana se pinte sobre sus ventanas secundarias.

Escritura del bucle de mensajes principal

El bucle de mensajes principal de una aplicación MDI es similar al de una aplicación que no es MDI que controla las teclas de aceleración. La diferencia es que el bucle de mensajes MDI llama a la función TranslateMDISysAccel antes de comprobar las teclas de aceleración definidas por la aplicación o antes de enviar el mensaje.

En el ejemplo siguiente se muestra el bucle de mensajes de una aplicación MDI típica. Tenga en cuenta que GetMessage puede devolver -1 si se produce un error.

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 función TranslateMDISysAccel traduce WM_KEYDOWN mensajes en mensajes WM_SYSCOMMAND y los envía a la ventana secundaria MDI activa. Si el mensaje no es un mensaje de aceleración de MDI, la función devuelve FALSE, en cuyo caso la aplicación usa la función TranslateAccelerator para determinar si se ha presionado alguna de las teclas de aceleración definidas por la aplicación. Si no es así, el bucle envía el mensaje al procedimiento de ventana adecuado.

Escribir el procedimiento de ventana marco

El procedimiento de ventana de una ventana de marco MDI es similar al de una ventana principal de una aplicación que no es MDI. La diferencia es que un procedimiento de ventana de marco pasa todos los mensajes que no controla a la función DefFrameProc en lugar de a la función DefWindowProc . Además, el procedimiento de ventana de marco también debe pasar algunos mensajes que controla, incluidos los enumerados en la tabla siguiente.

Message Response
WM_COMMAND Activa la ventana secundaria MDI que el usuario elige. Este mensaje se envía cuando el usuario elige una ventana secundaria MDI en el menú de ventana de la ventana marco MDI. El identificador de ventana que acompaña a este mensaje identifica la ventana secundaria MDI que se va a activar.
WM_MENUCHAR Abre el menú de ventana de la ventana secundaria MDI activa cuando el usuario presiona la combinación de teclas ALT+ – (menos).
WM_SETFOCUS Pasa el foco del teclado a la ventana del cliente MDI, que a su vez la pasa a la ventana secundaria MDI activa.
WM_SIZE Cambia el tamaño de la ventana del cliente MDI para que se ajuste al área de cliente de la nueva ventana de marco. Si el procedimiento de ventana de marco cambia el tamaño de la ventana del cliente MDI a un tamaño diferente, no debe pasar el mensaje a la función DefWindowProc .

 

El procedimiento de ventana de marco en Multipad se denomina MPFrameWndProc. El control de otros mensajes por MPFrameWndProc es similar al de las aplicaciones que no son MDI. WM_COMMAND los mensajes de Multipad se controlan mediante la función CommandHandler definida localmente. Para los mensajes de comando Multipad no controla, CommandHandler llama a la función DefFrameProc . Si Multipad no usa DefFrameProc de forma predeterminada, el usuario no puede activar una ventana secundaria desde el menú de la ventana, ya que el mensaje de WM_COMMAND enviado haciendo clic en el elemento de menú de la ventana se perdería.

Escribir el procedimiento de ventana secundaria

Al igual que el procedimiento de ventana de marco, un procedimiento de ventana secundaria MDI usa una función especial para procesar mensajes de forma predeterminada. Todos los mensajes que el procedimiento de ventana secundario no controla deben pasarse a la función DefMDIChildProc en lugar de a la función DefWindowProc . Además, algunos mensajes de administración de ventanas deben pasarse a DefMDIChildProc, incluso si la aplicación controla el mensaje, para que MDI funcione correctamente. A continuación se muestran los mensajes que la aplicación debe pasar a DefMDIChildProc.

Message Response
WM_CHILDACTIVATE Realiza el procesamiento de activación cuando las ventanas secundarias MDI tienen un tamaño, se mueven o se muestran. Este mensaje debe pasarse.
WM_GETMINMAXINFO Calcula el tamaño de una ventana secundaria MDI maximizada, en función del tamaño actual de la ventana del cliente MDI.
WM_MENUCHAR Pasa el mensaje a la ventana de marco MDI.
WM_MOVE Actualiza las barras de desplazamiento del cliente MDI, si están presentes.
WM_SETFOCUS Activa la ventana secundaria, si no es la ventana secundaria MDI activa.
WM_SIZE Realiza operaciones necesarias para cambiar el tamaño de una ventana, especialmente para maximizar o restaurar una ventana secundaria MDI. Si no se pasa este mensaje a la función DefMDIChildProc , se generan resultados muy no deseados.
WM_SYSCOMMAND Controla los comandos de menú de ventana (anteriormente conocido como sistema): SC_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZE y SC_MAXIMIZE.

 

Crear una ventana secundaria

Para crear una ventana secundaria de MDI, una aplicación puede llamar a la función CreateMDIWindow o enviar un mensaje de WM_MDICREATE a la ventana del cliente MDI. (La aplicación puede usar la función CreateWindowEx con el estilo WS_EX_MDICHILD para crear ventanas secundarias MDI). Una aplicación MDI de un solo subproceso puede usar cualquiera de los métodos para crear una ventana secundaria. Un subproceso de una aplicación MDI multiproceso debe usar la función CreateMDIWindow o CreateWindowEx para crear una ventana secundaria en un subproceso diferente.

El parámetro lParam de un mensaje de WM_MDICREATE es un puntero lejano a una estructura MDICREATESTRUCT . La estructura incluye cuatro miembros de dimensión: x e y, que indican las posiciones horizontales y verticales de la ventana, y cx y cy, que indican las extensiones horizontales y verticales de la ventana. Cualquiera de estos miembros puede ser asignado explícitamente por la aplicación, o bien se pueden establecer en CW_USEDEFAULT, en cuyo caso el sistema selecciona una posición, tamaño o ambos, según un algoritmo en cascada. En cualquier caso, se deben inicializar los cuatro miembros. Multipad usa CW_USEDEFAULT para todas las dimensiones.

El último miembro de la estructura MDICREATESTRUCT es el miembro de estilo , que puede contener bits de estilo para la ventana. Para crear una ventana secundaria MDI que pueda tener cualquier combinación de estilos de ventana, especifique el estilo de ventana MDIS_ALLCHILDSTYLES . Cuando no se especifica este estilo, una ventana secundaria MDI tiene los estilos WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLL y WS_VSCROLL como valores predeterminados.

Multipad crea sus ventanas secundarias MDI mediante su función AddFile definida localmente (ubicada en el archivo de origen MPFILE. C). La función AddFile establece el título de la ventana secundaria asignando el miembro szTitle de la estructura MDICREATESTRUCT de la ventana al nombre del archivo que se está editando o a "Sin título". El miembro szClass se establece en el nombre de la clase de ventana secundaria MDI registrada en la función InitializeApplication de Multipad. El miembro hOwner se establece en el identificador de instancia de la aplicación.

En el ejemplo siguiente se muestra la función AddFile en 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; 
} 

El puntero pasado en el parámetro lParam del mensaje WM_MDICREATE se pasa a la función CreateWindow y aparece como el primer miembro de la estructura CREATESTRUCT , pasado en el mensaje WM_CREATE . En Multipad, la ventana secundaria se inicializa durante WM_CREATE procesamiento de mensajes inicializando variables de documento en sus datos adicionales y creando la ventana secundaria del control de edición.