Como criar assistentes
Um assistente é um tipo de folha de propriedades que fornece uma maneira simples e poderosa de orientar os usuários através de um procedimento.
Os assistentes são uma das chaves para simplificar a experiência do usuário. Eles permitem que você faça uma operação complexa, como a configuração de um aplicativo, e a divida em uma série de etapas simples. Em cada ponto do processo, você pode fornecer uma explicação do que é necessário e exibir controles que permitem ao usuário fazer seleções e inserir texto.
Um assistente é, na verdade, um tipo de folha de propriedades. Uma folha de propriedades é essencialmente um contêiner para uma coleção de páginas, onde cada página é uma caixa de diálogo separada. Enquanto as folhas de propriedades regulares permitem que o usuário acesse qualquer página a qualquer momento, os assistentes apresentam páginas em sequência. Em vez de guias, os botões são usados para navegar para frente e para trás. A ordem em que as páginas são exibidas é controlada pelo aplicativo e pode ser modificada com base na entrada do usuário.
Há dois estilos principais de assistente: o estilo Wizard97 mais antigo e o estilo Aero introduzido no Windows Vista. Para obter ilustrações, consulte Sobre folhas de propriedades. (Um terceiro estilo, usando apenas o sinalizador PSH_WIZARD ou PSH_WIZARD_LITE, apresenta uma sequência simples de folhas de propriedades sem cabeçalhos ou marcas d'água.)
Observação
Uma "marca d'água" no contexto de assistentes é um bitmap que aparece na margem esquerda de algumas páginas.
A discussão na maioria deste documento pressupõe que você esteja implementando um assistente para um sistema com versão 5.80 ou posterior dos controles comuns. Se você tentar usar o estilo Wizard97 com versões anteriores dos controles comuns, seu aplicativo pode compilar, mas não será exibido corretamente. Para obter uma discussão sobre como criar um assistente compatível com Wizard97 em sistemas anteriores, consulte Assistentes compatíveis com versões anteriores mais adiante neste tópico.
- C/C++
- Programação da interface do usuário do Windows
A implementação de um assistente é semelhante à implementação de uma folha de propriedades regular. No nível mais básico, é uma questão de definir um dos seguintes sinalizadores ou combinações de sinalizadores na estrutura PROPSHEETHEADER que define a folha de propriedades.
Sinalizador | Estilo |
---|---|
PSH_WIZARD | Um assistente simples sem cabeçalhos ou bitmaps. |
PSH_WIZARD_LITE | Semelhante ao PSH_WIZARD, com algumas pequenas diferenças na aparência; Por exemplo, o divisor acima dos botões é definido para a largura total da janela. |
PSH_WIZARD97 | Um assistente do Wizard97 com cabeçalhos (opcionais), bitmaps de cabeçalho e marcas d'água. |
PSH_WIZARD | PSH_AEROWIZARD | Um Aero Wizard. Os Aero Wizards não usam marcas d'água ou bitmaps de cabeçalho. Eles exigem o modelo de apartamento de thread única (STA). |
O procedimento básico para implementar um assistente é o seguinte:
- Crie um modelo de caixa de diálogo para cada página.
- Defina as páginas criando uma estrutura PROPSHEETPAGE para cada página. Essa estrutura define a página e contém ponteiros para o modelo de caixa de diálogo e quaisquer bitmaps ou outros recursos.
- Passe a estrutura PROPSHEETPAGE criada na etapa anterior para a função CreatePropertySheetPage para criar o identificador HPROPSHEETPAGE da página.
- Defina o assistente criando uma estrutura PROPSHEETHEADER para ele.
- Passe a estrutura PROPSHEETHEADER para a função PropertySheet para exibir o assistente.
- Implemente procedimentos de caixa de diálogo para cada página para manipular mensagens de notificação dos controles da página e dos botões do assistente e para processar outras mensagens do Windows.
Existem dois tipos básicos de página de assistente: exterior e interior. As páginas externas são as páginas de introdução (bem-vindas) e de conclusão. Todos os outros são páginas interiores.
Modelos de caixa de diálogo da página externa
O layout básico das páginas de introdução e conclusão é idêntico. A ilustração a seguir mostra uma página de introdução do Wizard97 de exemplo, com uma marca d'água de espaço reservado.
Para páginas externas do Wizard97, o modelo de caixa de diálogo é 317x193 unidades de diálogo. Ele preenche todo o assistente, exceto a legenda e a faixa na parte inferior que contém os botões Voltar, Avançar e Cancelar. O lado esquerdo do modelo, que é reservado para um bitmap de "marca d'água", não deve conter nenhum controle. A marca d'água é especificada na estrutura PROPSHEETHEADER do assistente e é adicionada à página automaticamente. Você deve permitir espaço para ele ao criar o modelo de recurso.
Ao criar o bitmap de marca d'água, lembre-se de que a caixa de diálogo pode aumentar de tamanho se, por exemplo, o usuário escolher uma fonte grande do sistema. Idiomas diferentes também tendem a ter métricas de fontes diferentes. Quando a página cresce, a área reservada para a marca d'água fica proporcionalmente maior. No entanto, você não pode alterar o bitmap de marca d'água, nem o bitmap é esticado para preencher a área maior. Em vez disso, o bitmap é deixado em seu tamanho original na parte superior esquerda da área reservada. A parte da área reservada maior que não é coberta pela marca d'água é preenchida automaticamente com a cor do pixel superior esquerdo do bitmap.
Se você precisar ter bitmaps de marca d'água de tamanhos diferentes para métricas de fonte diferentes, duas soluções possíveis são:
- Obtenha as métricas de fonte antes de criar o assistente e especifique um bitmap de marca d'água de tamanho apropriado.
- Não especifique um bitmap de marca d'água ao criar o assistente. O Wizard97 deixará a área de marca d'água em branco. Em seguida, desenhe um bitmap de tamanho apropriado na área reservada para a marca d'água.
Você pode colocar controles na área à direita da marca d'água como faria para uma caixa de diálogo normal. A cor de fundo dessa área é determinada pelo sistema e não requer nenhuma ação de sua parte. Normalmente, você coloca dois controles estáticos nessa área. A parte superior contém o título e usa uma fonte grande em negrito (12 pontos Verdana Bold para Wizard97). O outro, que é para texto explicativo, usa a fonte da caixa de diálogo padrão.
A principal diferença entre as páginas de introdução e conclusão são os botões do assistente e o texto nos controles estáticos. As páginas de introdução normalmente têm um botão Avançar e um botão Voltar, com apenas o botão Avançar ativado. As páginas de conclusão têm o botão Voltar ativado e o botão Avançar é substituído por um botão Concluir botão.
Observação
No Aero Wizards, o botão Voltar é substituído por um botão de seta na barra de legenda.
Você pode modificar o texto no botão Concluir enviando ao assistente uma mensagem de PSM_SETFINISHTEXT. Por padrão, o botão Concluir não inclui um acelerador de teclado. Para definir um acelerador de teclado, inclua um e comercial na cadeia de texto que você passa para PSM_SETFINISHTEXT. Por exemplo, "&Finish" define "F" como o acelerador do teclado.
Modelos de caixa de diálogo da página interior
As páginas interiores têm uma aparência um pouco diferente das páginas exteriores. A ilustração a seguir mostra uma página interna do Wizard97 de exemplo, com um bitmap de cabeçalho de espaço reservado.
A área de cabeçalho na parte superior da página é manipulada pela folha de propriedades, portanto, não é incluída no modelo. O conteúdo do cabeçalho é especificado na estrutura PROPSHEETPAGE da página e na estrutura PROPSHEETHEADER do assistente. Como a página interna precisa se ajustar entre o cabeçalho e os botões, o modelo de caixa de diálogo Wizard97 é 317x143 unidades de diálogo, um pouco menor do que o modelo para páginas externas.
A ilustração a seguir mostra um Assistente Aero que foi criado a partir do mesmo modelo.
Depois de criar os modelos de caixa de diálogo e recursos relacionados, como bitmaps e tabelas de cadeia de caracteres, você pode criar as páginas de folha de propriedades. O procedimento é semelhante ao das folhas de propriedades padrão. Primeiro, preencha os membros apropriados de uma estrutura PROPSHEETPAGE (Alguns membros são específicos para assistentes.) Em seguida, chame a função CreatePropertySheetPage para criar o identificador HPROPSHEETPAGE da página.
Os sinalizadores relacionados ao assistente a seguir podem ser definidos no membro dwFlags da estrutura PROPSHEETPAGE.
Sinalizador | Descrição |
---|---|
PSP_HIDEHEADER | Defina este sinalizador para páginas externas no Wizard97. O cabeçalho não é mostrado e uma marca d'água pode ser mostrada. |
PSP_USEHEADERTITLE | Defina esse sinalizador para páginas interiores para colocar um título na área de cabeçalho no Wizard97 ou na parte superior da área do cliente em um Aero Wizard. |
PSP_USEHEADERSUBTITLE | Defina esse sinalizador para páginas interiores para colocar uma legenda na área de cabeçalho no Wizard97. |
Se você tiver definido PSP_USEHEADERTITLE ou PSP_USEHEADERSUBTITLE, atribua o título e o texto da legenda aos membros pszHeaderTitle e pszHeaderSubtitle, respectivamente. Ao atribuir cadeias de caracteres de texto a membros das estruturas PROPSHEETPAGE e PROPSHEETHEADER você pode atribuir um ponteiro de cadeia de caracteres ou usar a macro MAKEINTRESOURCE para atribuir um valor de um recurso de cadeia de caracteres. O recurso de cadeia de caracteres é carregado a partir do módulo especificado no membro hInstance da estrutura PROPSHEETHEADER do assistente.
Quando você chamar CreatePropertySheetPage para criar uma página, atribua o resultado a um elemento de uma matriz de identificadores HPROPSHEETPAGE. Essa matriz é usada ao criar a folha de propriedades. O índice de matriz do identificador de uma página determina a ordem padrão na qual ele é exibido. Depois de criar o identificador HPROPSHEETPAGE de uma página, você pode reutilizar a mesma estrutura PROPSHEETPAGE para criar a próxima página atribuindo novos valores aos membros relevantes.
Uma maneira alternativa de criar páginas é usar estruturas PROPSHEETPAGE separadas para cada página e criar uma matriz de estruturas. Essa matriz é usada em vez de uma matriz de identificadores HPROPSHEETPAGE ao criar a folha de propriedades. O uso de estruturas PROPSHEETPAGE separadas elimina a necessidade de chamar CreatePropertySheetPage mas usa mais memória. Caso contrário, não há diferença significativa entre as duas abordagens.
O exemplo a seguir define uma página interna do Wizard97 atribuindo valores a uma estrutura PROPSHEETPAGE estrutura. Neste exemplo, o título, o subtítulo e o modelo de caixa de diálogo da página são todos identificados por suas IDs de recurso. A função CreatePropertySheetPage é então chamada para criar o identificador HPROPSHEETPAGE da página. Como será a segunda página a aparecer, o identificador é atribuído à matriz de identificadores, ahpsp, com um índice de 1.
// g_hInstance is the global HINSTANCE of the application.
// IntPage1DlgProc is the dialog procedure for this page.
// ahpsp is an array of HPROPSHEETPAGE handles.
PROPSHEETPAGE psp = { sizeof(psp) };
psp.hInstance = g_hInstance;
psp.dwFlags = PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
psp.lParam = (LPARAM) &wizdata;
psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_TITLE1);
psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_SUBTITLE1);
psp.pszTemplate = MAKEINTRESOURCE(IDD_INTERIOR1);
psp.pfnDlgProc = IntPage1DlgProc;
ahpsp[1] = CreatePropertySheetPage(&psp);
Ao criar uma página, você pode atribuir dados personalizados a ela usando o membro lParam da estrutura PROPSHEETPAGE normalmente, atribuindo-lhe um ponteiro para uma estrutura definida pelo usuário.
Quando a página é selecionada pela primeira vez, seu procedimento de caixa de diálogo recebe uma mensagem WM_INITDIALOG. O valor lParam da mensagem aponta para uma cópia da estrutura PROPSHEETPAGE da página, a partir da qual você pode recuperar os dados personalizados. Em seguida, você pode armazenar esses dados para uso em mensagens subsequentes usando SetWindowLongPtr com GWL_USERDATA como o parâmetro de índice. Várias páginas podem ter um ponteiro para os mesmos dados, e qualquer alteração nos dados feita por uma página está disponível para as outras páginas em seus procedimentos de diálogo.
Assim como nas folhas de propriedades comuns, você define a folha de propriedades do assistente preenchendo membros de uma estrutura PROPSHEETHEADER Essa estrutura permite especificar as páginas que compõem o assistente e a ordem padrão em que elas são exibidas, juntamente com vários parâmetros relacionados. Em seguida, inicie o assistente chamando a função PropertySheet.
No estilo Wizard97, o membro pszCaption da estrutura PROPSHEETHEADER é ignorado. Em vez disso, o assistente exibe a legenda especificada no modelo de caixa de diálogo da página atual. Se o modelo não tiver uma legenda, a legenda da página anterior será exibida. Assim, para exibir a mesma legenda em todas as páginas, especifique a legenda no modelo para a página introdutória.
No estilo Assistente Aero, a legenda da caixa de diálogo é retirada de pszCaption.
Se você tiver criado uma matriz de identificadores HPROPSHEETPAGE para suas páginas, atribua a matriz ao membro phpage. Se, em vez disso, você tiver criado uma matriz de estruturas PROPSHEETPAGE atribua a matriz ao membro ppsp e defina o sinalizador PSH_PROPSHEETPAGE no membro dwFlags.
O exemplo a seguir atribui valores a psh, uma estrutura PROPSHEETHEADER e chama a função PropertySheet para iniciar o assistente. O assistente no estilo Wizard97 tem gráficos de marca d'água e cabeçalho, especificados por suas IDs de recurso. A matriz ahpsp contém todos os identificadores HPROPSHEETPAGE e define a ordem padrão em que eles são exibidos.
// g_hInstance is the global HINSTANCE of the application.
// ahpsp is an array of HPROPSHEETPAGE handles.
PROPSHEETHEADER psh = { sizeof(psh) };
psh.hInstance = g_hInstance;
psh.hwndParent = NULL;
psh.phpage = ahpsp;
psh.dwFlags = PSH_WIZARD97 | PSH_WATERMARK | PSH_HEADER;
psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
psh.pszbmHeader = MAKEINTRESOURCE(IDB_BANNER);
psh.nStartPage = 0;
psh.nPages = 4;
PropertySheet(&psh);
Cada página do assistente precisa de um procedimento de caixa de diálogo para processar mensagens do Windows, especialmente notificações de seus controles e do assistente. As três mensagens que quase todos os assistentes devem ser capazes de manipular são WM_INITDIALOG, WM_DESTROY e WM_NOTIFY.
A mensagem WM_NOTIFY é recebida antes que a página seja exibida e quando qualquer um dos botões do assistente for clicado. O parâmetro lParam da mensagem é um ponteiro para uma estrutura de cabeçalho NMHDR. O ID da notificação está contido no membro código da estrutura. As quatro notificações que a maioria dos assistentes precisa manipular são as seguintes.
Código | Descrição |
---|---|
PSN_SETACTIVE | Enviado antes da página ser exibida. |
PSN_WIZBACK | Enviado quando o botão Voltar é clicado. |
PSN_WIZNEXT | Enviado quando o botão Avançar é clicado. |
PSN_WIZFINISH | Enviado quando o botão Concluir é clicado. |
Quando uma página está prestes a ser exibida pela primeira vez, seu procedimento de caixa de diálogo recebe uma mensagem WM_INITDIALOG. A manipulação dessa mensagem permite que o assistente execute todas as tarefas de inicialização necessárias, como armazenar dados personalizados ou definir fontes.
Quando a folha de propriedades é destruída, você recebe uma mensagem WM_DESTROY. O assistente é destruído automaticamente pelo sistema, mas o manuseio dessa mensagem permite que você faça qualquer limpeza necessária.
O código de notificação PSN_SETACTIVE é enviado sempre que uma página está prestes a ficar visível. Na primeira vez que uma página é visitada, PSN_SETACTIVE segue a mensagem WM_INITDIALOG. Se a página for posteriormente revisitada, ela receberá apenas uma notificação PSN_SETACTIVE. Essa notificação geralmente é manipulada para inicializar dados para a página e habilitar os botões apropriados.
Por padrão, o assistente exibe os botões Voltar, Avançar e Cancelar com todos os botões ativados. Para desativar um botão ou exibir Concluir em vez de Avançar, você deve enviar uma mensagem de PSM_SETWIZBUTTONS. Depois que essa mensagem for enviada, o estado dos botões será preservado até que seja modificado por outra mensagem PSM_SETWIZBUTTONS, mesmo que uma nova página seja selecionada. Normalmente, todos os manipuladores de PSN_SETACTIVE enviam essa mensagem para garantir que cada página tenha o estado de botão correto.
Você pode alterar o estado do botão com essa mensagem a qualquer momento. Por exemplo, talvez você queira que o botão Avançar seja desabilitado inicialmente. Depois que um usuário inserir todas as informações necessárias, você poderá enviar outra mensagem de PSM_SETWIZBUTTONS para habilitar o botão Avançar e permitir que o usuário prossiga para a próxima página.
O fragmento de código a seguir usa a macro PropSheet_SetWizButtons para habilitar os botões Voltar e Avançar em uma página interna antes de ser exibido.
case WM_NOTIFY :
{
LPNMHDR pnmh = (LPNMHDR)lParam;
switch(pnmh->code)
{
...
case PSN_SETACTIVE :
...
// This is an interior page.
PropSheet_SetWizButtons(hwnd, PSWIZB_NEXT | PSWIZB_BACK);
...
}
...
}
Quando um botão Avançar ou Voltar é clicado, você recebe um código de notificação PSN_WIZNEXT ou PSN_WIZBACK. Por padrão, o assistente vai automaticamente para a página seguinte ou anterior na ordem definida quando a folha de propriedades é criada. Um motivo comum para lidar com essas notificações é impedir que o usuário mude de página ou substituir a ordem de página padrão.
Para impedir que o usuário alterne de página, manipule a notificação do botão, chame a função SetWindowLong com o valor DWL_MSGRESULT definido como –1 e retorne TRUE. Por exemplo:
case PSN_WIZNEXT :
...
// Do not go to the next page yet.
SetWindowLong(hwnd, DWL_MSGRESULT, -1);
return TRUE;
...
Para substituir a ordem padrão e ir para uma página específica, chame SetWindowLong com o valor DWL_MSGRESULT definido como ID de recurso da caixa de diálogo da página e retorne TRUE. Por exemplo:
case PSN_WIZNEXT :
...
// Go straight to the completion page.
SetWindowLong(hwnd, DWL_MSGRESULT, IDD_FINISH);
return TRUE;
...
Quando o botão Concluir ou Cancelar é clicado, você recebe um código de notificação PSN_WIZFINISH ou PSN_RESET, respectivamente. Quando um desses botões é clicado, o assistente é automaticamente destruído pelo sistema. No entanto, você pode lidar com essas notificações se precisar executar tarefas de limpeza antes que o assistente seja destruído. Para evitar que o assistente seja destruído quando você receber uma notificação de PSN_WIZFINISH, chame SetWindowLong com o valor de DWL_MSGRESULT definido como TRUE e retorne TRUE. Por exemplo:
case PSN_WIZFINISH :
...
// Not finished yet.
SetWindowLong(hwnd, DWL_MSGRESULT, TRUE);
return TRUE;
...
A seção anterior pressupõe que você esteja implementando um assistente para um sistema com versão 5 ou posterior dos controles comuns.
Se você estiver escrevendo um assistente para sistemas com versões anteriores dos controles comuns, muitos dos recursos discutidos na seção anterior não estarão disponíveis. Vários membros das estruturas PROPSHEETHEADER e PROPSHEETPAGE que são usados pelo estilo Wizard97 são suportados apenas por controles comuns versão 5 e posterior. No entanto, ainda é possível implementar um assistente compatível com versões anteriores com uma aparência semelhante à do estilo Wizard97. Para fazer isso, você deve implementar explicitamente o seguinte:
- Adicione o gráfico de marca d'água ao modelo de caixa de diálogo para suas páginas de introdução e conclusão.
- Faça todos os seus modelos do mesmo tamanho. Não há uma área de cabeçalho definida pelo sistema separada para páginas internas.
- Crie a área de cabeçalho da página interior explicitamente em seus modelos.
- Não use um gráfico de cabeçalho porque ele pode entrar em conflito com o título ou legenda se o assistente alterar o tamanho.
Para obter mais informações sobre assistentes compatíveis com versões anteriores, consulte Wizard97 compatível com versões anteriores.
Para obter uma discussão completa dos problemas de design do Wizard97, consulte a especificação Wizard97, em outro lugar do SDK do Windows. Este documento tem diretrizes para coisas como as dimensões das caixas de diálogo, dimensões e cores de bitmap e o posicionamento de controles.