Partilhar via


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 típico de interface de vários documentos (MDI).

Registrando classes de criança e janela de moldura

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

A estrutura de classe para a janela de quadro é semelhante à estrutura de classe para a janela principal em aplicativos não-MDI. A estrutura de classe para as janelas subordinadas MDI difere ligeiramente da estrutura para janelas subordinadas em aplicações que não utilizam MDI da seguinte maneira:

  • A estrutura de classe deve ter um ícone, porque o utilizador pode minimizar uma janela MDI filha como se fosse uma janela de aplicação normal.
  • O nome do menu deve ser NULL, porque uma janela filha MDI não pode ter o seu próprio menu.
  • A estrutura da classe deve reservar espaço extra na estrutura da janela. Com este espaço, a aplicação pode associar dados, como um nome de ficheiro, a uma janela filha específica.

O exemplo seguinte mostra como o Multipad regista as suas classes de moldura e janelas filhas.

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 moldura e secundárias

Depois de registrar suas classes de janela, um aplicativo MDI pode criar suas janelas. Primeiro, ele cria a janela da moldura usando a função CreateWindow ou a função 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 da classe da janela do cliente; MDICLIENT é uma classe de janela pré-registrada definida pelo sistema. O parâmetro lpvParam de CreateWindow ou CreateWindowEx deve apontar para uma estrutura CLIENTCREATESTRUCT. Esta estrutura contém os membros descritos na tabela a seguir:

Membro Descrição
hWindowMenu Manipule o menu da janela usado para controlar as janelas filho do MDI. À medida que as janelas filho são criadas, o aplicativo adiciona seus títulos ao menu da janela como itens de menu. O utilizador pode então ativar uma janela secundária clicando no seu título no menu de janelas.
idFirstChild Especifica o identificador da primeira janela filha MDI. A primeira janela filho MDI criada recebe esse identificador. Janelas adicionais são criadas com identificadores de janela incrementados. Quando uma janela secundária é destruída, o sistema reatribui imediatamente os identificadores de janela para manter a continuidade do seu intervalo.

 

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

O procedimento da janela de moldura do Multipad cria a janela cliente MDI durante o processamento da mensagem 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 secundárias 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). Uma aplicação MDI que adiciona strings ao seu menu de janela deve usar a função InsertMenu e verificar se os títulos das janelas filhas não substituíram essas novas strings.

Use o estilo WS_CLIPCHILDREN para criar a janela do cliente MDI para impedir que a janela pinte sobre suas janelas filhas.

Escrevendo o loop de mensagem principal

O loop de mensagem principal de um aplicativo MDI é semelhante ao de um aplicativo não-MDI manipulando chaves aceleradoras. A diferença é que o loop de mensagem MDI chama a função TranslateMDISysAccel antes de verificar se há teclas de atalho definidas pelo aplicativo ou antes de enviar 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çãoTranslateMDISysAccel converte mensagens WM_KEYDOWN em mensagens WM_SYSCOMMAND e as envia para a janela filha MDI ativa. Se a mensagem não for uma mensagem de acelerador MDI, a função retornará FALSE , caso em que o aplicativo usa a funçãoTranslateAccelerator para determinar se alguma das teclas de aceleração definidas pelo aplicativo foi pressionada. Caso contrário, o loop envia a mensagem para o procedimento de janela apropriado.

Escrevendo o procedimento da janela de quadro

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

Mensagem Resposta
WM_COMMAND Ativa a janela filho MDI escolhida pelo usuário. Esta mensagem é enviada quando o utilizador escolhe uma janela filha MDI no menu de janela da janela de moldura MDI. O identificador de janela que acompanha esta mensagem identifica a janela filho MDI a ser ativada.
WM_MENUCHAR Abre o menu da janela da janela secundária MDI ativa quando o utilizador 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 do cliente da nova janela principal. Se o procedimento da janela de moldura redimensionar a janela do cliente MDI para um tamanho diferente, não deve passar a mensagem para a função DefWindowProc.

 

O procedimento de janela de moldura no Multipad é chamado MPFrameWndProc. O tratamento de outras mensagens por MPFrameWndProc é semelhante ao de aplicativos não-MDI. As mensagens WM_COMMAND no Multipad são tratadas pela função localmente definida CommandHandler. Para as mensagens de comando que o Multipad não manipula, o 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, porque a mensagem WM_COMMAND enviada clicando no item de menu da janela será perdida.

Escrever o procedimento da janela filha

Como no procedimento de janela de moldura, um procedimento de janela filho MDI usa uma função especial para processar mensagens por padrão. Todas as mensagens que o procedimento da janela filho não manipula devem ser passadas para a função DefMDIChildProc em vez de serem encaminhadas para a função DefWindowProc. Além disso, algumas mensagens de gerenciamento de janela devem ser passadas para DefMDIChildProc, mesmo que o aplicativo manipule a mensagem, para que o 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 as janelas filho MDI são dimensionadas, movidas ou exibidas. Esta mensagem deve ser passada.
WM_GETMINMAXINFO Calcula o tamanho de uma janela MDI filho maximizada, com base no tamanho atual da janela 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 filha, se esta não for a janela filha MDI ativa.
WM_SIZE Executa as operações necessárias para alterar o tamanho de uma janela, especialmente para maximizar ou restaurar uma janela filha MDI. Falhar em passar esta mensagem para a função DefMDIChildProc produz resultados altamente indesejáveis.
WM_SYSCOMMAND Manipula comandos de menu da janela (anteriormente conhecido como sistema): SC_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZEe SC_MAXIMIZE.

 

Criando uma janela filha

Para criar uma janela MDI filha, uma aplicação 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 filha MDI.) Uma aplicação MDI de thread único pode usar qualquer método para criar uma janela filha. Uma thread em um aplicativo MDI multithreaded deve usar a função CreateMDIWindow ou a função CreateWindowEx para criar uma janela filha num thread diferente.

O parâmetro lParam de uma mensagem WM_MDICREATE é um ponteiro longínquo para uma estrutura MDICREATESTRUCT. A estrutura inclui quatro membros de dimensão: x e y, que indicam as posições horizontais e verticais 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 eles podem ser definidos para CW_USEDEFAULT, caso em que o sistema seleciona uma posição, tamanho, ou ambos, de acordo com um algoritmo em cascata. Em qualquer caso, todos os quatro membros devem ser inicializados. 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 filha MDI que possa ter qualquer combinação de estilos de janela, especifique o estilo de janela MDIS_ALLCHILDSTYLES. Quando este estilo não é especificado, uma janela filha MDI possui os estilos WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLLe WS_VSCROLL como configurações padrão.

O Multipad cria suas janelas filhas 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 filha, atribuindo o membro szTitle da estruturaMDICREATESTRUCTda janela ao nome do ficheiro que está a ser editado ou a "Sem título". O membro szClass é definido como o nome da classe de janela filha MDI registada 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 no 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 que é passado no parâmetro lParam da mensagem WM_MDICREATE é transmitido para a função CreateWindow, aparecendo como o primeiro membro da estrutura CREATESTRUCT, que é passada na mensagem WM_CREATE. No Multipad, a janela filha inicializa-se durante o WM_CREATE processamento de mensagens ao inicializar variáveis do documento nos seus dados extras da janela filha e ao criar a janela filha do controlo de edição.