Usando a interface de vários documentos
Esta seção explica como executar as seguintes tarefas:
- Registrando classes de janela filho e quadro
- Criando janelas de quadro e filho
- Gravando o loop de mensagem principal
- Gravando o procedimento de janela de quadro
- Gravando o procedimento da janela filho
- Criando uma janela filho
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.