Usando a interface de vários documentos

Esta seção explica como executar as seguintes tarefas:

Para ilustrar essas tarefas, esta seção inclui exemplos do Multipad, um aplicativo MDI (interface MDI) típico.

Registrando classes de janela filho e quadro

Um aplicativo MDI típico deve registrar duas classes de janela: uma para sua janela de quadro e outra para suas janelas filho. Se um aplicativo der suporte a mais de um tipo de documento (por exemplo, uma planilha e um gráfico), ele deverá registrar uma classe de janela para cada tipo.

A estrutura de classe para a janela de quadro é semelhante à estrutura de classe da janela main em aplicativos não MDI. A estrutura de classe para as janelas filho MDI difere ligeiramente da estrutura para janelas filho em aplicativos não MDI da seguinte maneira:

  • A estrutura de classe deve ter um ícone, pois o usuário pode minimizar uma janela filho MDI como se fosse uma janela normal do aplicativo.
  • O nome do menu deve ser NULL, pois uma janela filho MDI não pode ter seu próprio menu.
  • A estrutura de classe deve reservar espaço extra na estrutura da janela. Com esse espaço, o aplicativo pode associar dados, como um nome de arquivo, a uma janela filho específica.

O exemplo a seguir mostra como o Multipad registra suas classes de janela filho e quadro.

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

Criando janelas de quadro e filho

Depois de registrar suas classes de janela, um aplicativo MDI pode criar suas janelas. Primeiro, ele cria sua janela de quadros usando a função CreateWindow ou CreateWindowEx . Depois de criar sua janela de quadro, o aplicativo cria sua janela de cliente, novamente usando CreateWindow ou CreateWindowEx. O aplicativo deve especificar MDICLIENT como o nome de classe da janela do cliente; MDICLIENT é uma classe de janela pré-registrado definida pelo sistema. O parâmetro lpvParam de CreateWindow ou CreateWindowEx deve apontar para uma estrutura CLIENTCREATESTRUCT . Essa estrutura contém os membros descritos na tabela a seguir:

Membro Descrição
hWindowMenu Identificador para o menu de janela usado para controlar janelas filho MDI. À medida que janelas filho são criadas, o aplicativo adiciona seus títulos ao menu da janela como itens de menu. Em seguida, o usuário pode ativar uma janela filho clicando em seu título no menu da janela.
idFirstChild Especifica o identificador da primeira janela filho MDI. A primeira janela filho MDI criada recebe esse identificador. Janelas adicionais são criadas com identificadores de janela incrementados. Quando uma janela filho é destruída, o sistema reatribui imediatamente os identificadores de janela para manter seu intervalo contíguo.

 

Quando o título de uma janela filho é adicionado ao menu da janela, o sistema atribui um identificador à janela filho. Quando o usuário clica no título de uma janela filho, a janela de quadro recebe uma mensagem WM_COMMAND com o identificador no parâmetro wParam . Você deve especificar um valor para o membro idFirstChild que não está em conflito com identificadores de item de menu no menu da janela de quadro.

O procedimento da janela de quadros do multipad cria a janela do cliente MDI durante o processamento da mensagem de WM_CREATE . O exemplo a seguir mostra como a janela do cliente é criada.

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; 

Os títulos das janelas filho são adicionados à parte inferior do menu da janela. Se o aplicativo adicionar cadeias de caracteres ao menu da janela usando a função AppendMenu , essas cadeias de caracteres poderão ser substituídas pelos títulos das janelas filho quando o menu da janela for repintado (sempre que uma janela filho for criada ou destruída). Um aplicativo MDI que adiciona cadeias de caracteres ao menu de janela deve usar a função InsertMenu e verificar se os títulos das janelas filho não substituiram essas novas cadeias de caracteres.

Use o estilo WS_CLIPCHILDREN para criar a janela do cliente MDI para impedir que a janela seja pintada sobre suas janelas filho.

Gravando o loop de mensagem principal

O loop de mensagem main de um aplicativo MDI é semelhante ao de um aplicativo não MDI que manipula teclas de acelerador. A diferença é que o loop de mensagem MDI chama a função TranslateMDISysAccel antes de verificar se há chaves de acelerador definidas pelo aplicativo ou antes de expedir a mensagem.

O exemplo a seguir mostra o loop de mensagem de um aplicativo MDI típico. Observe que GetMessage pode retornar -1 se houver um erro.

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

A função TranslateMDISysAccel converte WM_KEYDOWN mensagens em mensagens WM_SYSCOMMAND e as envia para a janela filho MDI ativa. Se a mensagem não for uma mensagem de acelerador MDI, a função retornará FALSE; nesse caso, o aplicativo usará a função TranslateAccelerator para determinar se alguma das teclas de acelerador definidas pelo aplicativo foi pressionada. Caso contrário, o loop envia a mensagem para o procedimento de janela apropriado.

Gravando o procedimento de janela de quadro

O procedimento de janela para uma janela de quadro MDI é semelhante ao de uma janela de main de um aplicativo não MDI. A diferença é que um procedimento de janela de quadro passa todas as mensagens que ele não manipula para a função DefFrameProc em vez de para a função DefWindowProc . Além disso, o procedimento de janela de quadro também deve passar algumas mensagens que ele manipula, incluindo aquelas listadas na tabela a seguir.

Mensagem Resposta
WM_COMMAND Ativa a janela filho MDI que o usuário escolhe. Essa mensagem é enviada quando o usuário escolhe uma janela filho MDI no menu de janela da janela de quadro MDI. O identificador de janela que acompanha essa mensagem identifica a janela filho MDI a ser ativada.
WM_MENUCHAR Abre o menu de janela da janela filho MDI ativa quando o usuário pressiona a combinação de teclas ALT+ – (menos).
WM_SETFOCUS Passa o foco do teclado para a janela do cliente MDI, que, por sua vez, o passa para a janela filho MDI ativa.
WM_SIZE Redimensiona a janela do cliente MDI para caber na área de cliente da nova janela de quadro. Se o procedimento da janela de quadro dimensionar a janela do cliente MDI para um tamanho diferente, ele não deverá passar a mensagem para a função DefWindowProc .

 

O procedimento de janela de quadro no Multipad é chamado MPFrameWndProc. O tratamento de outras mensagens por MPFrameWndProc é semelhante ao de aplicativos não MDI. WM_COMMAND mensagens no Multipad são manipuladas pela função CommandHandler definida localmente. Para mensagens de comando que o Multipad não manipula, CommandHandler chama a função DefFrameProc . Se o Multipad não usar DefFrameProc por padrão, o usuário não poderá ativar uma janela filho no menu da janela, pois a mensagem WM_COMMAND enviada clicando no item de menu da janela seria perdida.

Gravando o procedimento da janela filho

Assim como o procedimento da janela de quadro, um procedimento de janela filho MDI usa uma função especial para processar mensagens por padrão. Todas as mensagens que o procedimento de janela filho não manipula devem ser passadas para a função DefMDIChildProc em vez de para a função DefWindowProc . Além disso, algumas mensagens de gerenciamento de janelas devem ser passadas para DefMDIChildProc, mesmo que o aplicativo manipule a mensagem, para que a MDI funcione corretamente. A seguir estão as mensagens que o aplicativo deve passar para DefMDIChildProc.

Mensagem Resposta
WM_CHILDACTIVATE Executa o processamento de ativação quando janelas filho MDI são dimensionadas, movidas ou exibidas. Essa mensagem deve ser passada.
WM_GETMINMAXINFO Calcula o tamanho de uma janela filho MDI maximizada, com base no tamanho atual da janela do cliente MDI.
WM_MENUCHAR Passa a mensagem para a janela de quadro MDI.
WM_MOVE Recalcula as barras de rolagem do cliente MDI, se elas estiverem presentes.
WM_SETFOCUS Ativa a janela filho, se não for a janela filho MDI ativa.
WM_SIZE Executa as operações necessárias para alterar o tamanho de uma janela, especialmente para maximizar ou restaurar uma janela filho MDI. Não passar essa mensagem para a função DefMDIChildProc produz resultados altamente indesejáveis.
WM_SYSCOMMAND Manipula comandos de menu de janela (anteriormente conhecido como sistema): SC_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZE e SC_MAXIMIZE.

 

Criando uma janela filho

Para criar uma janela filho MDI, um aplicativo pode chamar a função CreateMDIWindow ou enviar uma mensagem WM_MDICREATE para a janela do cliente MDI. (O aplicativo pode usar a função CreateWindowEx com o estilo WS_EX_MDICHILD para criar janelas filho MDI.) Um aplicativo MDI de thread único pode usar qualquer método para criar uma janela filho. Um thread em um aplicativo MDI multithreaded deve usar a função CreateMDIWindow ou CreateWindowEx para criar uma janela filho em um thread diferente.

O parâmetro lParam de uma mensagem WM_MDICREATE é um ponteiro distante para uma estrutura MDICREATESTRUCT . A estrutura inclui quatro membros de dimensão: x e y, que indicam as posições horizontal e vertical da janela, e cx e cy, que indicam as extensões horizontal e vertical da janela. Qualquer um desses membros pode ser atribuído explicitamente pelo aplicativo ou pode ser definido como CW_USEDEFAULT, caso em que o sistema seleciona uma posição, tamanho ou ambos, de acordo com um algoritmo em cascata. De qualquer forma, todos os quatro membros devem ser inicializados. O multipad usa CW_USEDEFAULT para todas as dimensões.

O último membro da estrutura MDICREATESTRUCT é o membro de estilo , que pode conter bits de estilo para a janela. Para criar uma janela filho MDI que possa ter qualquer combinação de estilos de janela, especifique o estilo da janela MDIS_ALLCHILDSTYLES . Quando esse estilo não é especificado, uma janela filho MDI tem os estilos WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLL e WS_VSCROLL como configurações padrão.

O Multipad cria suas janelas filho MDI usando sua função AddFile definida localmente (localizada no arquivo de origem MPFILE. C). A função AddFile define o título da janela filho atribuindo o membro szTitle da estrutura MDICREATESTRUCT da janela ao nome do arquivo que está sendo editado ou a "Untitled". O membro szClass é definido como o nome da classe de janela filho MDI registrada na função InitializeApplication do Multipad. O membro hOwner é definido como o identificador de instância do aplicativo.

O exemplo a seguir mostra a função AddFile em 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; 
} 

O ponteiro passado no parâmetro lParam da mensagem WM_MDICREATE é passado para a função CreateWindow e aparece como o primeiro membro na estrutura CREATESTRUCT , passado na mensagem WM_CREATE . No Multipad, a janela filho se inicializa durante WM_CREATE processamento de mensagens inicializando variáveis de documento em seus dados extras e criando a janela filho do controle de edição.