Inno Setup (Parte 6): Página personalizada I
Escrito por Carlos B. Feitoza Filho | |
Categoria: Tutoriais | |
Categoria Pai: Addicted 2 Delphi! | |
Acessos: 14796 |
Muita gente acredita que sistemas de instalação padronizados, como o Inno Setup, só são capazes de exibir suas próprias páginas e que normalmente quando se vê um instalador cheio de opções avançadas e telas não ortodoxas, como acontece com o instalador do Delphi por exemplo, isso só é possível utilizando sistemas mais complexos (e pagos) como o Install Shield ou porque são instaladores feitos do zero por alguém que gosta de sofrer bastante[1], ou, não menos pior, por alguém que quer testar seus vastos conhecimentos em programação no tocante à distribuição e instalação de sistemas.
A verdade é que com o Inno Setup você pode modificar páginas existentes (páginas padrão) ou mesmo criar qualquer número de páginas adicionais, com quaisquer controles que permitem a interação do usuário. Este é um tópico avançado deste tutorial que eu pretendo abordar da forma mais didática quanto possível.
O Inno Setup Form Designer
Há alguns anos, quando descobri que era possível adicionar e modificar páginas ao/no instalador havia um programa que facilitava a tarefa de definição do layout das telas adicionais que eu queria incluir. O programa chamava-se Inno Setup Form Designer (ISFD) e, como o nome sugere, com ele eu posso "desenhar" as telas adicionais. Explicações são desnecessárias quando se pode ver uma imagem do ISFD:
Como se pode observar sua interface lembra a IDE do Delphi, com uma paleta de componentes e um Object Inspector. Seu uso é tão simples como a nossa amada IDE, basta arrastar os componentes na área delimitada.
Ao contrário da IDE do Delphi, onde a criação de telas e seu código-fonte é totalmente visual, no ISFD o desenho da tela serve apenas para geração do código que precisará ser incluído manualmente no nosso script. Note que existem ali no ISFD algumas páginas além da página atual (Custom Form), são elas:
- Declaração
- Exibe todas as variáveis necessárias para a implementação da tela que foi desenhada
- Exibe todas as variáveis necessárias para a implementação da tela que foi desenhada
- Implementação
- Exibe os códigos de criação de cada um dos controles adicionados a tela que foi desenhada de forma que se possa, no script, criar dinamicamente a nossa tela exatamente da forma como ela foi desenhada
- Exibe os códigos de criação de cada um dos controles adicionados a tela que foi desenhada de forma que se possa, no script, criar dinamicamente a nossa tela exatamente da forma como ela foi desenhada
- Custom Messages
- O ISFD trabalha com strings constantes da forma correta, isto é, para cada string existente no desenho da tela (captions, labels, etc.) ele cria uma custom message que pode ser usada para criar um instalador multi-idiomas. As custom messages não serão abordadas neste tutorial, por isso, em momento oportuno será solicitado que se copie o conteúdo desta página na seção CustomMessages do nosso script sem qualquer explicação adicional
- O ISFD trabalha com strings constantes da forma correta, isto é, para cada string existente no desenho da tela (captions, labels, etc.) ele cria uma custom message que pode ser usada para criar um instalador multi-idiomas. As custom messages não serão abordadas neste tutorial, por isso, em momento oportuno será solicitado que se copie o conteúdo desta página na seção CustomMessages do nosso script sem qualquer explicação adicional
- Código Exemplo
- Nesta página é exibido um modelo de script completo como exemplo do que deve ou pode ser feito no nosso script para que a página personalizada apareça. Você verá aqui referências a funções de evento do Inno Setup e como usá-las para inicializar a página personalizada
Nada melhor para entender o ISFD do que instalá-lo e executá-lo. Ele ainda vem com uma série de scripts e telas de exemplo que você pode explorar à vontade. Se você ainda não o instalou, faça isso agora antes de passar para a próxima seção. O link para download se encontra dentre os anexos ao final deste artigo.
Mãos à obra!
Chega de falar, programador Delphi que se preze gosta mesmo é de ação, então abra agora o ISFD e clique no item de menu Arquivo > Novo Formulário. A tela do ISFD estará vazia e você pode então adicionar os controles tal como na imagem abaixo:
Aproveite a oportunidade e salve logo este projeto como PGConfig.isf na mesma pasta onde está o nosso script (ZOISE.iss). Não é necessário salvar este arquivo na mesma pasta do script, mas eu sugiro isso apenas para facilitar.
A imagem acima não mostra, mas eu configurei o TPanel que contém o texto para ter a cor de fundo clInfoBk. O ISFD não é perfeito, mas ele é até então a única ferramenta disponível que me permite criar de forma simples, páginas para o Inno Setup. Não usar ele significa ter que lidar com código de criação dinâmica de componentes a partir do zero, o que tornaria o desenvolvimento da página personalizada extremamente maçante. Considere então o ISFD como a ferramenta que fará 99% do trabalho duro (alguns chamam esse tipo de ferramenta de C.A.S.E.). Mais adiante eu direi o que precisa ser alterado diretamente no script para que a página personalizada apareça como queremos.
Na imagem acima é possível alterar onde se lê Caption e Description (na faixa branca), porém o ISFD não possui estas propriedades (O ISFD não é perfeito. Você leu o quadro anterior?). Apesar disso, ele cria duas custom messages para que seja possível, via código, alterar estes dois valores. Vamos alterar estas duas custom messages posteriormente.
E assim criamos a nossa primeira tela personalizada do Inno Setup sem qualquer esforço, o qual ficou por conta do ISFD que criou para nós todo o código necessário:
[CustomMessages]
PGConfig_Caption=PGConfig Caption
PGConfig_Description=PGConfig Description
PGConfig_LABEPGUsername_Caption0=Nome de usuário
PGConfig_LABEPGPassword_Caption0=Senha
PGConfig_LABEPGPort_Caption0=Porta
PGConfig_LABEPGServiceName_Caption0=Nome do serviço a ser instalado
PGConfig_EDITPGUserName_Text0=postgres
PGConfig_LABEExplanation_Caption0=Preencha abaixo as informações pretendidas para a instalação do PostgreSQL. Estas informações serão usadas durante a inicialização da instância do PostgreSQL, durante a instalação de seu serviço e ao inicializar este serviço
PGConfig_EDITPGPassword_Text0=123456
PGConfig_EDITPGPort_Text0=5400
PGConfig_EDITPGServiceName_Text0=InnoSetupPG
[Code]
var
LABEPGUsername: TLabel;
LABEPGPassword: TLabel;
LABEPGPort: TLabel;
LABEPGServiceName: TLabel;
EDITPGUserName: TEdit;
PANEExplanation: TPanel;
LABEExplanation: TLabel;
EDITPGPassword: TEdit;
EDITPGPort: TEdit;
EDITPGServiceName: TEdit;
{ PGConfig_Activate }
procedure PGConfig_Activate(Page: TWizardPage);
begin
// enter code here...
end;
{ PGConfig_ShouldSkipPage }
function PGConfig_ShouldSkipPage(Page: TWizardPage): Boolean;
begin
Result := False;
end;
{ PGConfig_BackButtonClick }
function PGConfig_BackButtonClick(Page: TWizardPage): Boolean;
begin
Result := True;
end;
{ PGConfig_NextkButtonClick }
function PGConfig_NextButtonClick(Page: TWizardPage): Boolean;
begin
Result := True;
end;
{ PGConfig_CancelButtonClick }
procedure PGConfig_CancelButtonClick(Page: TWizardPage; var Cancel, Confirm: Boolean);
begin
// enter code here...
end;
{ PGConfig_CreatePage }
function PGConfig_CreatePage(PreviousPageId: Integer): Integer;
var
Page: TWizardPage;
begin
Page := CreateCustomPage(
PreviousPageId,
ExpandConstant('{cm:PGConfig_Caption}'),
ExpandConstant('{cm:PGConfig_Description}')
);
{ LABEPGUsername }
LABEPGUsername := TLabel.Create(Page);
with LABEPGUsername do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:PGConfig_LABEPGUsername_Caption0}');
Left := ScaleX(6);
Top := ScaleY(61);
Width := ScaleX(80);
Height := ScaleY(13);
end;
{ LABEPGPassword }
LABEPGPassword := TLabel.Create(Page);
with LABEPGPassword do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:PGConfig_LABEPGPassword_Caption0}');
Left := ScaleX(91);
Top := ScaleY(61);
Width := ScaleX(30);
Height := ScaleY(13);
end;
{ LABEPGPort }
LABEPGPort := TLabel.Create(Page);
with LABEPGPort do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:PGConfig_LABEPGPort_Caption0}');
Left := ScaleX(177);
Top := ScaleY(61);
Width := ScaleX(26);
Height := ScaleY(13);
end;
{ LABEPGServiceName }
LABEPGServiceName := TLabel.Create(Page);
with LABEPGServiceName do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:PGConfig_LABEPGServiceName_Caption0}');
Left := ScaleX(225);
Top := ScaleY(61);
Width := ScaleX(152);
Height := ScaleY(13);
end;
{ EDITPGUserName }
EDITPGUserName := TEdit.Create(Page);
with EDITPGUserName do
begin
Parent := Page.Surface;
Left := ScaleX(6);
Top := ScaleY(75);
Width := ScaleX(80);
Height := ScaleY(21);
TabOrder := 0;
Text := ExpandConstant('{cm:PGConfig_EDITPGUserName_Text0}');
end;
{ PANEExplanation }
PANEExplanation := TPanel.Create(Page);
with PANEExplanation do
begin
Parent := Page.Surface;
Left := ScaleX(6);
Top := ScaleY(6);
Width := ScaleX(401);
Height := ScaleY(46);
BevelInner := bvRaised;
BevelOuter := bvLowered;
Color := -16777192;
TabOrder := 4;
end;
{ LABEExplanation }
LABEExplanation := TLabel.Create(Page);
with LABEExplanation do
begin
Parent := PANEExplanation;
Caption := ExpandConstant('{cm:PGConfig_LABEExplanation_Caption0}');
Left := ScaleX(2);
Top := ScaleY(2);
Width := ScaleX(397);
Height := ScaleY(42);
Align := alClient;
Alignment := taCenter;
WordWrap := True;
end;
{ EDITPGPassword }
EDITPGPassword := TEdit.Create(Page);
with EDITPGPassword do
begin
Parent := Page.Surface;
Left := ScaleX(91);
Top := ScaleY(75);
Width := ScaleX(80);
Height := ScaleY(21);
PasswordChar := '*';
TabOrder := 1;
Text := ExpandConstant('{cm:PGConfig_EDITPGPassword_Text0}');
end;
{ EDITPGPort }
EDITPGPort := TEdit.Create(Page);
with EDITPGPort do
begin
Parent := Page.Surface;
Left := ScaleX(177);
Top := ScaleY(75);
Width := ScaleX(43);
Height := ScaleY(21);
TabOrder := 2;
Text := ExpandConstant('{cm:PGConfig_EDITPGPort_Text0}');
end;
{ EDITPGServiceName }
EDITPGServiceName := TEdit.Create(Page);
with EDITPGServiceName do
begin
Parent := Page.Surface;
Left := ScaleX(225);
Top := ScaleY(75);
Width := ScaleX(181);
Height := ScaleY(21);
TabOrder := 3;
Text := ExpandConstant('{cm:PGConfig_EDITPGServiceName_Text0}');
end;
with Page do
begin
OnActivate := @PGConfig_Activate;
OnShouldSkipPage := @PGConfig_ShouldSkipPage;
OnBackButtonClick := @PGConfig_BackButtonClick;
OnNextButtonClick := @PGConfig_NextButtonClick;
OnCancelButtonClick := @PGConfig_CancelButtonClick;
end;
Result := Page.ID;
end;
{ PGConfig_InitializeWizard }
procedure InitializeWizard();
begin
PGConfig_CreatePage(wpWelcome);
end;
Acima podemos ver que o ISFD criou as custom messages (linhas 2 a 12), bem como todo o código necessário (a partir da linha 14) para que a página seja exibida durante a instalação. Se você for um leitor atento vai perceber que até mesmo alguns eventos estão sendo definidos para nossa página personalizada.
As linhas 16 a 25 mostram a declaração das variáveis de cada um dos controles que utilizamos nesta página. As linhas 29 a 60 contém as definições dos manipuladores para os eventos comuns que uma página do instalador deve manipular. Nem todos estes eventos serão utilizados na nossa implementação. As linhas 64 a 215 definem uma função que é responsável por criar a página personalizada com todos os controles que definimos em tempo de design no ISFD.
Usando o código gerado pelo ISFD
O que faremos agora é copiar o código gerado pelo ISFD e colá-lo no nosso script adaptando-o quando necessário. Para começar, abra o script no Inno Script Studio e clique no item Inno Setup Script no Tree View Sections. O editor na parte central da tela mostrará todo script que criamos até o momento:
Copie o texto das linhas 1 a 12 do script gerado pelo ISFD imediatamente antes da seção [Code] já existente no script, tal como mostra a imagem acima. Você poderia colocar a seção [CustomMessages] em qualquer lugar no script desde que seja antes da seção [Code]. Eu sugeri que ela ficasse imediatamente antes da seção [Code] apenas para termos um ponto de referência simples e também porque vamos falar muito a respeito da seção [Code] daqui para a frente neste tutorial.
Observe que as duas primeiras custom mensages (PGConfig_Caption e PGConfig_Description) foram definidas. Estas mensagens correspondem ao Caption e o Description vistos na faixa branca acima da página personalizada, na tela do ISFD, portanto, ao alterar estes dois valores, estaremos alterando os valores que aparecem acima da nossa página personalizada em tempo de execução de nosso instalador. O restante das custom messages são referentes a captions e outras strings constantes definidas em tempo de projeto no ISFD, por exemplo, PGConfig_EDITPGPassword_Text0 contém o valor da senha padrão (123456) configurada na propriedade Text do TEdit de nome EDITPGPassword.
Copie o texto das linhas 15 a 25 do script gerado pelo ISFD imediatamente após a seção [Code], antes do código pré-existente:
Como foi dito anteriormente, estas são as declarações de variáveis de cada um dos controles utilizados na nossa página personalizada. O próximo passo é colar logo abaixo da declaração das variáveis o código contido entre as linhas 29 e 60 do script gerado pelo ISFD. Este código contém apenas os manipuladores dos seguintes eventos:
- OnActivate - Este evento acontece sempre que a nossa página personalizada for exibida e corresponderia a um evento OnShow em um TForm convencional
- OnShouldSkipPage - Este evento acontece imediatamente antes da nossa página personalizada ser exibida e deve ser usado para informar ao Inno Setup se nossa página precisa ou não ser exibida. Ao retornar True, a página não será exibida
- OnBackButtonClick - Este evento acontece quando pressionamos o botão Back enquanto nossa página personalizada está sendo exibida. Ao retornar False, a página anterior não será carregada, isto é, você permanecerá na página atual (nossa página personalizada). Você pode usar este evento para impedir que o usuário retorne a páginas anteriores. Não consegui achar um bom exemplo de uso disso, mas a funcionalidade existe, caso você a queira usar
- OnNextButtonClick - Este evento acontece quando pressionamos o botão Next enquanto nossa página personalizada está sendo exibida. Ao retornar False, a próxima página não será carregada, isto é, você permanecerá na página atual (nossa página personalizada). Você pode usar este evento para realizar validações nos dados informados pelo usuário e assim só permitir a continuidade do instalador apenas se estes dados estiverem corretos
- OnCancelButtonClick - Este evento acontece quando pressionamos o botão Cancel enquanto nossa página personalizada está sendo exibida. Este evento tem dois parâmetros adicionais Cancel e Confirm. Ao configurar Cancel como False, não será possível fechar o instalador, seja pressionando o botão Cancel, seja fechando o instalador clicando no "X" da janela. Ao configurar Confirm como False, não será exibida uma mensagem perguntando se o usuário deseja fechar o instalador. Caso Cancel seja False, o parâmetro Config será ignorado por motivos óbvios
Todos os manipuladores para os eventos citados acima possuem o parâmetro Page do tipo TWizardPage, que representa a página atual e pode ser usado de diversas formas dentro do manipulador do evento para obter ou configurar propriedades da página. Leia a ajuda do Inno Setup procurando pelo termo TWizardPage para saber quais são as propriedades desta classe.
Ao colar os textos dos manipuladores de eventos remova os comentários que existem entre chaves para melhorar a legibilidade do código. Ao final o bloco de código colado ficará como na imagem abaixo:
Agora vamos colar em nosso script a função PGConfig_CreatePage, responsável por, efetivamente, criar nossa página personalizada e incluí-la dentre as páginas preexistentes do Inno Setup. Copie o código entre as linhas 64 a 215 do script gerado pelo ISFD e cole estas linhas no script do Inno Setup imediatamente após os manipuladores de evento, tal como pode ser visto na imagem abaixo:
Obviamente não é possível exibir toda a função na imagem acima, a qual serve apenas para indicar onde esta função deve ficar, isto é, após o manipulador do evento OnCancelButtonClick (PGConfig_CancelButtonClick).
Anteriormente eu havia falado que o ISFD, por ser antigo, não é perfeito. Ele não manipula todas as propriedades que o Pascal Script disponibiliza atualmente. Uma destas propriedades é a ParentBackground do componente TPanel. Se você olhar o código de criação do TPanel que contém nosso texto dentro da função PGConfig_CreatePage, vai notar que ele está assim:
PANEExplanation := TPanel.Create(Page);
with PANEExplanation do
begin
Parent := Page.Surface;
Left := ScaleX(6);
Top := ScaleY(6);
Width := ScaleX(401);
Height := ScaleY(46);
BevelInner := bvRaised;
BevelOuter := bvLowered;
Color := -16777192;
TabOrder := 4;
end;
Apesar deste código estar essencialmente correto ele não faz menção a propriedade ParentBackground, que é responsável por tornar o TPanel "opaco", com a cor que escolhemos. Note que até mesmo a propriedade Color está definida com a cor escolhida (-16777192 é a cor clInfoBk), mas essa propriedade se torna inefetiva sem a presença da propriedade ParentBackground, portanto, edite este trecho de código e inclua a propriedade ParentBackground, tal como é mostrado a seguir:
PANEExplanation := TPanel.Create(Page);
with PANEExplanation do
begin
Parent := Page.Surface;
Left := ScaleX(6);
Top := ScaleY(6);
Width := ScaleX(401);
Height := ScaleY(46);
BevelInner := bvRaised;
BevelOuter := bvLowered;
Color := -16777192;
TabOrder := 4;
ParentBackground := False; // Faz com que a propriedade Color seja efetiva!
end;
Após esta pequena correção é hora de salvar o script e tentar compilá-lo. Se você seguiu à risca os passos, a compilação deverá ser bem sucedida, do contrário, resolva os problemas existentes antes de continuar. Você já deve ter conhecimento suficiente para identificar problemas de compilação do Inno Setup (aka READ THE FUCK ERROR MESSAGES), então faça uso deste conhecimento para resolver qualquer problema que surja. Caso não consiga resolver, fique à vontade para usar os comentários no final do artigo.
Agora que temos em nosso script as variáveis, os manipuladores de eventos exclusivos da página personalizada e a função que efetivamente cria a essa página, é hora de informar ao Inno Setup que queremos incluí-la dentre suas páginas preexistentes. Felizmente o ISFD também gera este código, o qual pode ser visto entre as linhas 219 e 222 do script gerado por ele, portanto, copie esta linhas e cole-as no final do nosso script:
Na imagem acima vemos a implementação do procedure InitializeWizard, tal como ele foi gerado pelo ISFD, mas com uma diferença, o parâmetro da função de criação de nossa página personalizada. A função PGConfig_CreatePage tem um parâmetro que precisa ser informado e indica o ID da página após a qual nossa página personalizada deve aparecer. Por padrão o ISFD coloca outro valor neste parâmetro (normalmente wpWelcome), por isso precisamos alterá-lo para que a nossa página apareça no local correto.
A escolha de um local correto dentro do fluxo do instalador depende bastante do que nosso instalador está fazendo. No caso específico do exemplo sendo montado neste tutorial, o melhor local onde uma página de configuração do PostgreSQL pode ficar é após a página de seleção de tarefas de instalação e isso faz todo sentido porque em partes anteriores deste tutorial foi criada uma tarefa específica para permitir a configuração do PostgreSQL, portanto, o valor deste parâmetro será a constante wpSelectTasks, que identifica a página de seleção de tarefas. Usar a constante wpSelectTasks garante também que nossa página personalizada aparecerá antes da tela de resumo de instalação (wpReady), logo, se quiséssemos poderíamos alterar o conteúdo desta tela de resumo, incluindo na mesma aquilo que fora configurado para o PostgreSQL em nossa página personalizada (se minha preguiça deixar e minha memória ajudar eu ensino como fazer isso também depois).
Todas as páginas exibidas no Inno Setup tem um ID exclusivo e constantes que as identificam. Isso nos permite incluir páginas personalizadas usando como referências estas constantes ao executar a função CreateCustomPage (vide primeiras linhas da função PGConfig_CreatePage). A função CreateCustomPage retorna uma página vazia pronta para inclusão de controles e esta página tem uma propriedade ID que é também a identificação única da página personalizada. Dessa forma podemos incluir qualquer número de páginas personalizadas uma após a outra, sempre usando o ID da página anteriormente criada como referência, usando-a como primeiro parâmetro da função CreateCustomPage
Após o ajuste do parâmetro de PGConfig_CreatePage, podemos finalmente executar nosso instalador apenas para vermos a nossa página personalizada. Avance pelo instalador pressionando Avançar > até chegar na nossa tela personalizada. Ela deverá aparecer imediatamente após a tela de exibição de tarefas ou após a tela anterior a tela de exibição de tarefas, caso esta última não apareça:
Fantástico, não é? Eu também acho, no entanto essa página ainda é burra, pois nada que ela provê está sendo de fato usado. Vamos agora começar a deixar esta página realmente funcional. Tenho certeza que você está se empolgando ;)
Ajustando a página personalizada
A primeira coisa que precisamos fazer para ajustar nossa página personalizada dentro do nosso instalador é instruí-la a aparecer apenas quando for necessário. Isso é imprescindível porque nosso instalador possui uma tarefa específica que, se marcada, fará o PostgreSQL ser configurado, logo, nossa tela só deve aparecer caso a tarefa em questão tenha sido marcada pelo usuário durante a instalação.
Atualmente esta tarefa já está sendo usada internamente para realizar a configuração automática do PostgreSQL, sem a interação do usuário, o qual apenas decide se o PostgreSQL deve ou não ser configurado. Como agora temos uma tela para entrada de parâmetros de configuração do PostgreSQL, haverá uma pequena mudança nessa lógica que vai passar a funcionar da seguinte forma:
- Caso a tarefa de configurar o PostgreSQL tenha sido marcada, a página Configuração do PostgreSQL será exibida com os parâmetros padrão, os quais são os mesmo usados atualmente na configuração automática. O usuário poderá optar por usar as configurações como estão ou poderá alterá-las e pressionar Avançar > para prosseguir com a instalação.
- Caso a tarefa de configurar o PostgreSQL não tenha sido marcada, a página Configuração do PostgreSQL NÃO SERÁ EXIBIDA e o PostgreSQL não será configurado na máquina de destino, ficando por conta do usuário realizar a inicialização e configuração do PostgreSQL posteriormente de forma manual tal como explicado no artigo Instalação e configuração manual do PostgreSQL no Windows
Para que a nossa página personalizada apenas apareça quando a tarefa de configuração do PostgreSQL estiver selecionada, edite o código do manipulador PGConfig_ShouldSkipPage de forma que ele fique como mostrado abaixo:
function PGConfig_ShouldSkipPage(Page: TWizardPage): Boolean;
begin
Result := not IsTaskSelected('PGConfig');
end;
O código acima mostra uma função do Inno Setup chamada IsTaskSelected, a qual retorna True, caso a tarefa (ou tarefas) indicada em seu parâmetro tenha sido selecionada pelo usuário durante a instalação. Como o manipulador PGConfig_ShouldSkipPage deve retornar True caso a página deva ser "pulada" (não exibida), devemos inverter a lógica de forma que se a tarefa PGConfig não estiver selecionada, a página Configuração do PostgreSQL não vai aparecer.
Se você executar o instalador novamente e testar este comportamento vai constatar que a página personalizada só aparece quando marcamos a tarefa "Configurar o PostgreSQL".
Acabamos de configurar uma condição de entrada na nossa página personalizada. Que tal agora configurarmos uma condição de saída? Que tal digitarmos algum código para validar aquilo que o usuário digitou? Fazer isso é tão simples como a condição de entrada, basta alterar o código do manipulador PGConfig_NextButtonClick, o qual é executado ao pressionarmos o botão Avançar > enquanto nossa tela estiver sendo exibida:
function PGConfig_NextButtonClick(Page: TWizardPage): Boolean;
var
Port: Longint;
begin
Result := False;
Port := StrToIntDef(EDITPGPort.Text,0);
if Trim(EDITPGUserName.Text) = '' then
MsgBox('É necessário informar um nome de usuário para o PostgreSQL',mbError,0)
else if Trim(EDITPGPassword.Text) = '' then
MsgBox('É necessário informar uma senha para o usuário do PostgreSQL',mbError,0)
else if Trim(EDITPGPort.Text) = '' then
MsgBox('É necessário informar a porta através da qual o PostgreSQL irá aguardar requisições',mbError,0)
else if Port = 0 then
MsgBox('O valor informado (' + EDITPGPort.Text + ') não é uma port válida',mbError,0)
else if Trim(EDITPGServiceName.Text) = '' then
MsgBox('É necessário informar um nome de serviço para o PostgreSQL',mbError,0)
else
begin
Result := True;
end;
end;
Como se pode observar, fizemos uso das variáveis de controles que foram declaradas, mas você poderia usar o parâmetro Page da função PGConfig_NextButtonClick para percorrer os controles de forma dinâmica, fazendo casts, caso você seja masoquista.
Na linha 5 definimos o retorno padrão da função como False, o que significa que, caso não alteremos o valor de Result, não será possível passar para a próxima página, pois clicar em Avançar > não vai funcionar. Na linha 7 eu utilizei um artifício para detectar se a porta digitada em EDITPGPort é um número válido. Caso aquilo que o usuário digitar no controle não seja um número válido, a variável Port vai receber zero, e eu poderei verificar isso mais adiante para detectar se foi digitada uma porta válida. As linhas 9 a 18 fazem efetivamente a verificação daquilo que o usuário digitou, verificando se os controles estão vazios e se a porta informada é válida. Note que eu fiz uso de uma função chamada MsgBox, que é um wrapper simplificado da função de API MessageBox e serve, claro, para exibir mensagens simples (Verifique a ajuda do Inno Setup para saber como usar esta e outras funções). Na linha 21, finalmente, nós alteramos o Result para True, de forma a instruir que está tudo OK e o pressionamento do botão Avançar > vai efetivamente carregar a próxima página. O código entre as linhas 20 e 22 será alterado mais adiante, mas primeiro eu tenho duas observações a fazer:
Você deve ter notado que eu não usei um método para mudar o foco para o controle com o dado errado (SetFocus). Isso é normalmente feito no Delphi mas o Pascal Script, ao menos dentro do Inno Setup, não dá suporte a este método. Eu tentei usar mensagens de baixo nível (SendMessage), mas o resultado não foi satisfatório porque aparentemente o Inno Setup mantém o foco nos botões do Wizard, por isso resolvi tirar essa implementação, mantendo as coisas mais simples, mas sem a facilidade de colocar o foco no controle que precisa ter o seu valor alterado. Até o presente momento, esta é a única limitação do Inno Setup com relação a páginas personalizadas e ainda assim isso é uma falha tão irrelevante que não refuta em nada a utilização do Inno Setup para criar qualquer tipo de instalador, do mais simples ao mais complexo!
Se você é um programador atento, deve ter achado estupidez de minha parte verificar daquela forma se foi informado um valor numérico válido e se você for um desenvolvedor preconceituoso que gosta de fazer instaladores manualmente[2], você deve estar achando que não é possível limitar os caracteres digitados em um TEdit. Bom, lamento informar, mas vocês dois estão errados. O TEdit no Pascal Script possui sim o evento OnKeyPress e eu poderia limitar a digitação a apenas caracteres numéricos, mas eu deixei isso de fora propositalmente para propor a você, caro leitor, que tente fazer isso por conta própria (o que seria da vida sem alguns desafios...). No próprio script gerado pelo ISFD (o qual estamos usando) existe alguns exemplos de como conectar eventos a seus manipuladores. O único alerta que faço é: o Pascal Script não é Pascal, por isso ele não possui todas as características da linguagem. Como o seu nome diz, ele é uma linguagem de script, portanto, simplificada. Apesar de muitos códigos simples rodarem da mesma forma entre o Pascal e o Pascal Script, isso não é uma regra, logo, seu manipulador do evento OnKeyPress vai ser ligeiramente diferente. Você vai digitar um pouco mais para conseguir o efeito desejado. Pronto. Já falei de mais...
Quando o pressionamento do botão Avançar > é bem sucedido o fluxo de execução entra na linha 21 que atualmente só possui a alteração do valor da variável de retorno da função PGConfig_NextButtonClick para True, que indica que a próxima página pode ser exibida. Além de permitir a exibição da próxima página, será necessário salvar os valores que foram informados na página personalizada em variáveis para facilitar o acesso por todo o script. Para isso, vamos criar 4 variáveis do tipo String. Vá até o início do código, próximo a seção [Code] e após as variáveis pré-existentes (dos controles) declare as 4 variáveis como no trecho de código a seguir:
[Code]
var
LABEPGUsername: TLabel;
LABEPGPassword: TLabel;
LABEPGPort: TLabel;
LABEPGServiceName: TLabel;
EDITPGUserName: TEdit;
PANEExplanation: TPanel;
LABEExplanation: TLabel;
EDITPGPassword: TEdit;
EDITPGPort: TEdit;
EDITPGServiceName: TEdit;
// Variáveis para os dados informados na página personalizada
PGUserName: String;
PGPassword: String;
PGPort: String;
PGServiceName: String;
Após a declaração das 4 variáveis é hora de atualizar o código do manipulador PGConfig_NextButtonClick, que, finalmente, ficará desta forma:
function PGConfig_NextButtonClick(Page: TWizardPage): Boolean;
var
Port: Longint;
begin
Result := False;
Port := StrToIntDef(EDITPGPort.Text,0);
if Trim(EDITPGUserName.Text) = '' then
MsgBox('É necessário informar um nome de usuário para o PostgreSQL',mbError,0)
else if Trim(EDITPGPassword.Text) = '' then
MsgBox('É necessário informar uma senha para o usuário do PostgreSQL',mbError,0)
else if Trim(EDITPGPort.Text) = '' then
MsgBox('É necessário informar a porta através da qual o PostgreSQL irá aguardar requisições',mbError,0)
else if Port = 0 then
MsgBox('O valor informado (' + EDITPGPort.Text + ') não é uma port válida',mbError,0)
else if Trim(EDITPGServiceName.Text) = '' then
MsgBox('É necessário informar um nome de serviço para o PostgreSQL',mbError,0)
else
begin
// Configurando as variáveis com valores válidos
PGUserName := EDITPGUserName.Text;
PGPassword := EDITPGPassword.Text;
PGPort := EDITPGPort.Text;
PGServiceName := EDITPGServiceName.Text;
Result := True;
end;
end;
Não há nada de especial no código acima. Qualquer programador Delphi entende o que está sendo feito e agora que temos variáveis para facilitar o acesso àquilo que foi informado pelo usuário durante a instalação, vamos usá-las para montar os comandos que estavam até então estáticos.
Lendo dados mantidos pelo Pascal Script a partir do Inno Setup
Não adiantaria nada todo esse esforço descrito até aqui se não fosse possível que o Inno Setup e suas seções pudessem ler dados mantidos (criados ou modificados) pelo Pascal Script. A forma de fazer com que as seções do Inno Setup enxerguem, por exemplo, variáveis disponíveis na seção [Code] (Pascal Script) é por meio de uma constante especial chamada {code:...} a qual tem uma sintaxe de utilização muito simples:
{code:FunctionName|param}
A constante {code:...} funciona executando uma função que recebe um parâmetro do tipo string e devolve uma string em resposta, portanto, no código acima, "FunctionName" precisa ter a assinatura function FunctionName(AParam: String): String e "param" é aquilo que precisa ser passado para essa função como parâmetro do tipo string. Ainda no código acima, "FunctionName" pode ser uma função criada por nós na seção [Code] ou pode ser uma função de suporte do próprio Inno Setup, desde que ela possua a assinatura requerida, como por exemplo a função de suporte "GetShortName", com a qual a sintaxe do comando poderia ser "{code:GetShortName|{app}}".
Explicado o funcionamento da constante {code:...}, fica claro que, primeiramente, precisamos criar a função que vai retornar os valores que queremos acessar. Como atualmente precisamos acessar apenas os parâmetros de configuração do PostgreSQL uma escolha óbvia para o nome desta função seria PGConfiguration, logo, sua definição completa ficaria da seguinte forma:
function PGConfiguration(AParam: String): String;
begin
Result := '';
if LowerCase(AParam) = 'username' then
Result := PGUserName
else if LowerCase(AParam) = 'password' then
Result := PGPassword
else if LowerCase(AParam) = 'port' then
Result := PGPort
else if LowerCase(AParam) = 'servicename' then
Result := PGServiceName;
end;
Esta função é extremamente simples. Por padrão ela retorna uma string vazia, mas se o parâmetro especificado em AParam for um dos parâmetros possíveis, a função vai então retornar o valor da variável correspondente. Agora ficou claro porque precisamos criar uma variável para cada configuração do PostgreSQL na nossa página personalizada; isso serviu para facilitar a criação desta função. Essa função pode ser colocada no final do script definido na seção [Code].
Agora que temos a função que nos permite acessar as variáveis de configuração do PostgreSQL, precisamos utilizá-la dentro de itens de outras seções do Inno Setup onde atualmente estamos passando valores estáticos. No artigo Inno Setup (Parte 5): Configurando o PostgreSQL, nós estamos configurando o PostgreSQL na seção [Run] (Install Run) e o estamos "desconfigurando" na seção [UninstallRun], logo, precisaremos alterar os itens destas duas seções. Para fazer isso eu prefiro editar diretamente o script, pois acho mais rápido, mas você pode editar os itens um a um a partir da interface visual do Inno Script Studio.
A imagem acima mostra como acessar o script completo do nosso instalador. Dentro deste script procure (CTRL+F) pela seção [Run]. Nesta seção você vai encontrar 4 itens, dos quais apenas os 3 últimos dizem respeito a configuração do PostgreSQL e podem ser vistos a seguir:
Filename: "{app}\pg\bin\initdb.exe"; Parameters: "-U postgres -A password -E utf8 -D ""{app}\pg\data"" --pwfile=""{tmp}\senha.xyz"""; WorkingDir: "{app}\pg\bin"; StatusMsg: "Inicializando a estrutra de pastas dentro da pasta ""data"" do PostgreSQL"; Components: PostgreSQL; Tasks: PGConfig
Filename: "{app}\pg\bin\pg_ctl.exe"; Parameters: "register -N ""InnoSetupPG"" -U ""NT AUTHORITY\NetworkService"" -D ""{app}\pg\data"" -w -o ""-p 5400"""; WorkingDir: "{app}\pg\bin"; StatusMsg: "Registrando o serviço do PostgreSQL"; Components: PostgreSQL; Tasks: PGConfig
Filename: "{sys}\net.exe"; Parameters: "start ""InnoSetupPG"""; WorkingDir: "{sys}"; StatusMsg: "Iniciando o serviço do PostgreSQL"; Components: PostgreSQL; Tasks: PGConfig
Até o presente momento, como se pode observar, a configuração do PostgreSQL automaticamente era feita com parâmetros estáticos (usuário = postgres, nome do serviço = InnoSetupPG, porta = 5400, etc.). Tudo que precisamos fazer agora é substituir tais parâmetros estáticos por chamadas a constante {code:...}. Abaixo é mostrado como ficarão estas 3 linhas após a utilização da constante {code:...} em cada uma delas:
Filename: "{app}\pg\bin\initdb.exe"; Parameters: "-U {code:PGConfiguration|username} -A password -E utf8 -D ""{app}\pg\data"" --pwfile=""{tmp}\senha.xyz"""; WorkingDir: "{app}\pg\bin"; StatusMsg: "Inicializando a estrutra de pastas dentro da pasta ""data"" do PostgreSQL"; Components: PostgreSQL; Tasks: PGConfig
Filename: "{app}\pg\bin\pg_ctl.exe"; Parameters: "register -N ""{code:PGConfiguration|servicename}"" -U ""NT AUTHORITY\NetworkService"" -D ""{app}\pg\data"" -w -o ""-p {code:PGConfiguration|port}"""; WorkingDir: "{app}\pg\bin"; StatusMsg: "Registrando o serviço do PostgreSQL"; Components: PostgreSQL; Tasks: PGConfig
Filename: "{sys}\net.exe"; Parameters: "start ""{code:PGConfiguration|servicename}"""; WorkingDir: "{sys}"; StatusMsg: "Iniciando o serviço do PostgreSQL"; Components: PostgreSQL; Tasks: PGConfig
Como se pode observar, trata-se apenas de substituição simples de texto, por exemplo, no texto original onde havia -U postgres passou a ser -U {code:PGConfiguration|username}, que significa neste ponto coloque o valor da configuração do PostgreSQL de nome "username".
O mesmo procedimento de substituição precisa ser feito na seção [UninstallRun], a qual possui apenas dois itens:
Filename: "{sys}\net.exe"; Parameters: "stop ""InnoSetupPG"""; WorkingDir: "{sys}"; Components: PostgreSQL; Tasks: PGConfig
Filename: "{app}\pg\bin\pg_ctl.exe"; Parameters: "unregister -N ""InnoSetupPG"""; WorkingDir: "{app}\pg\bin"; Components: PostgreSQL; Tasks: PGConfig
Desta vez, ambos os itens dizem respeito a desinstalação do PostgreSQL, logo, ambos precisam ser alterados. Após a substituição, esta seção será como no trecho a seguir:
Filename: "{sys}\net.exe"; Parameters: "stop ""{code:PGConfiguration|servicename}"""; WorkingDir: "{sys}"; Components: PostgreSQL; Tasks: PGConfig
Filename: "{app}\pg\bin\pg_ctl.exe"; Parameters: "unregister -N ""{code:PGConfiguration|servicename}"""; WorkingDir: "{app}\pg\bin"; Components: PostgreSQL; Tasks: PGConfig
Isso conclui toda alteração necessária para que as seções [Run] e [UninstallRun] possam utilizar aquilo que o usuário informou durante a instalação, mas algo ainda precisa ser feito.
Se você for atento, você deve ter notado que em nenhum momento nós alteramos qualquer parâmetro que diga respeito a senha do superusuário do PostgreSQL. No artigo Inno Setup (Parte 5): Configurando o PostgreSQL, foi explicado que a única forma de passar a senha do superusuário do PostgreSQL via linha de comando, seria por meio de um arquivo que contém a senha. Neste artigo foi criado e utilizado um procedure que, em momento oportuno, cria o arquivo senha.xyz no diretório temporário de instalação. O conteúdo deste arquivo é simplesmente a senha do superusuário do PostgreSQL de forma plana (sem criptografia alguma). O procedure em questão chama-se CriarArquivoDeSenha e convenientemente ele possui um parâmetro que é justamente a senha que deve ser salva nele, logo, não precisamos mudar o procedure, mas sim sua chamada, a qual está sendo feita dentro da função PrepareToInstall. De volta a seção [Code] nós podemos observar esta função antes de sua alteração:
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
// Cria o arquivo de senha com a senha especificada
CriarArquivoDeSenha('123456');
end;
Como se pode ver, originalmente a senha era fixa e igual a 123456. Como agora queremos usar aquilo que o usuário informa na nossa página personalizada, a chamada ao procedure CriarArquivoDeSenha ficará da seguinte forma:
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
// Cria o arquivo de senha com a senha especificada
CriarArquivoDeSenha(PGPassword);
end;
Como nós já estamos dentro da seção [Code] (dentro do Pascal Script) tudo que precisamos fazer é acessar diretamente a variável que contém a senha informada pelo usuário na página personalizada.
O teste
Agora sim, execute o instalador novamente e durante a instalação informe parâmetros de configuração diferentes para o PostgreSQL, por exemplo:
Note que todos os valores são diferentes dos valores padrão, justamente para comprovação de que todos estes valores estão sendo realmente configurados. Após alterar os valores, continue a instalação e a conclua. Em seguida, vá até a lista de serviços do Windows e procure o serviço de nome ServicoPostgreSQL:
Como se pode observar, um serviço com o nome especificado por nós durante a instalação foi corretamente criado e iniciado. Ao executar um duplo clique na linha do nosso serviço, podemos ver outras propriedades e comprovar mais um dos parâmetros que foi corretamente substituído:
Nesta tela podemos ver novamente o nome do serviço e agora também podemos ver a porta 5001, que foi configurada durante a instalação. Agora vamos usar o PgAdmin para verificar se o serviço está funcionando e comprovar mais um parâmetros que configuramos:
Podemos ver que a conexão foi estabelecida com o usuário root. Vemos novamente nesta tela a porta onde a conexão ocorreu (5001). Não será possível comprovar que a senha funcionou, mas pode confiar em mim. Eu usei a senha 123 que foi configurada durante a instalação ;)
Os screenshots acima comprovam que o PostgreSQL foi instalado e configurado conforme nossas instruções, logo, até o momento nós comprovamos que aquilo que é feito na seção [Run] utiliza de fato os parâmetros que nós informamos, mas agora precisamos comprovar que os parâmetros também funcionam na seção [UninstallRun] e para isso, basta desinstalar o nosso exemplo, portanto, vá até o Painel de Controle do Windows e desinstale o Zetta-Ømnis Inno Setup Example versão 1.0. Após realizar a desinstalação, verifique na lista de serviços do Windows que o serviço que havia sido instalado não mais existe e no PgAdmin, ao tentar conectar ao servidor anteriormente funcional, nós recebemos a seguinte mensagem de erro:
Agora sim, foi totalmente comprovado que os dados informados na nossa página personalizada estão sendo 100% utilizados em todos os locais que nós definimos. Sucesso absoluto!
Opinião pessoal
Hoje aprendemos uma das características menos utilizadas do Inno Setup mas ao mesmo tempo uma das mais legais e versáteis. Todo o meu esforço na elaboração deste tutorial tinha como meta o assunto das páginas personalizadas porque eu considero que esta é a diferença entre um instalador "burro" e algo que realmente faz exatamente o que você deseja!
Dentro desse contexto, eu não consigo entender porque algumas pessoas (empresas ou individuais) preferem lidar com instaladores feitos de forma artesanal ao invés de usar aquilo que foi criado especificamente para isso. Na minha mente é inconcebível que se tenha mais trabalho para realizar uma determinada tarefa quando se tem outras dezenas de formas de fazer a mesma coisa com um mínimo de esforço. Eu tento, eu juro que tento compreender a motivação destas pessoas, mas mesmo me esforçando muito eu só consigo chegar a conclusões que me levam a fazer comentários jocosos a respeito delas.
Se você se identificou com isso que eu falei, não se sinta diminuído. Talvez o que tenha lhe faltado tenha sido apenas aquele incentivo mínimo, aquele empurrãozinho, sendo assim, use esta série de artigos para fazer do seu instalador algo realmente profissional, não apenas por fora, mas também por dentro :)
Como eu falei anteriormente sobre simplicidade, eu preciso citar uma pessoa que certa vez falou a respeito disso:
Pense a respeito ;)
Isso é só o começo...
As opções de personalização do Inno Setup são extensas e muito poderosas. Nas próximas partes deste tutorial eu pretendo falar como alterar de forma simples páginas pré-existentes do Inno Setup, porque tudo aquilo que é bom sempre pode ficar melhor. Espero ver vocês todos lá :)