Tchau ShowMessage, MessageBox e MessageDlg, Olá TaskDialog

Por muito tempo, a maneira mais fácil de criar um aviso textual foi usando o ShowMessage(‘Hello World’). Claro que o ShowMessage não vem sozinho, com eles uma série de janelas de dialogos que podemos ver no link abaixo:

https://wiki.freepascal.org/Dialog_Examples

Neste artigo vamos ver como substituir o ShowMessage, MessageBox e MessageDlg por uma variação moderna chamada de TaskDialog que funciona no Lazarus e seu comportamento é o mesmo em qualquer sistema operacional. A documentação do TaskDialog pode ser encontrada aqui:

https://wiki.freepascal.org/TTaskDialog

Para usar o TaskDialog, basta incluir a unit Dialogs ao seu uses, na realidade, ela é tão comum que provavelmente você já a possui em todos os seus projetos e apenas desperdiça a possibilidade de usar TaskDialog. Você pode usar o componente TaskDialog, mas perceberá que é mais fácil evitar o componente e ir implementá-lo com código.

Porque usar TaskDialog?

A resposta mais simples de porque usar o TaskDialog é porque ele pode ser usado em todas as situações de interação com o usuário, enquanto as outras formas de Dialogs usam comandos diferentes para cada situação e no caso do MessageBox e MessageDlg, o texto em seus botões podem não estar traduzidos causando o efeito indesejado de pergunta em português e tendo que clicar em Yes, No, Cancel…

Com o TaskDialog é possível acrescentar ícone indicador, titulo de janela, titulo de texto, botões, radiogroups, … deixando-o com cara de Assistente(Wizard). Veja esse exemplo de como o TaskDialog pode ficar se incluir todos os recursos:

Podemos configurar a janela de dialogo para incluir o que desejarmos

ShowMessage, MessageBox e MessageDlg – a queda

O ShowMessage é usado dessa forma:

ShowMessage('Não há quem goste de dor, que a procure e a queira ter, simplesmente porque é dor...');

E exibido assim para o usuário:

Não tem beleza alguma e basta então clicar em “OK” e a janela será finalizada. Uma forma diferente de exibir essa mesma mensagem usando o TaskDialog é:

  with TTaskDialog.Create(self) do
  try
    Caption := 'Titulo da janela';
    Title := 'Titulo do texto';
    Text := 'Não há quem goste de dor, que a procure e a queira ter, simplesmente porque é dor...';
    CommonButtons := [tcbOk];   // (tcbOk, tcbYes, tcbNo, tcbCancel, tcbRetry, tcbClose);
    MainIcon := tdiInformation;    // (tdiNone, tdiWarning, tdiError, tdiInformation, tdiShield, tdiQuestion);
    Execute;
  finally
    Free;
  end  
TaskDialog sendo exibida como mensagem no Windows

Agora temos um titulo na janela, temos um titulo para o texto e o texto propriamente dito. Sei o que está pensando: “mas é muito mais código para só mostrar uma mensagem”. O que você não está pensando é:

  • Posso refatorar o TaskDialog para substituir ShowMessage
  • Totalmente personalizável

Como assim refatorar? Simplesmente Crie um procedimento chamado ShowMessage2 que use o TaskDialog:

procedure ShowMessage2(
  ATexto: String; 
  ATitle: String='Informação:'; 
  ACaption: String='');
begin
  if ACaption=emptyStr
    then ACaption:=ExtractFileName(Application.ExeName);
  if ATitle=emptyStr
    then ATitle:='Informação:';
  with TTaskDialog.Create(self) do
  try
    Caption := ACaption;
    Title := ATitle;
    Text := ATexto;
    CommonButtons := [tcbOk];   // (tcbOk, tcbYes, tcbNo, tcbCancel, tcbRetry, tcbClose);
    MainIcon := tdiInformation;    // (tdiNone, tdiWarning, tdiError, tdiInformation, tdiShield, tdiQuestion);
    Execute;
  finally
    Free;
  end
end;

E pronto, agora posso substituir todos os ShowMessages por ShowMessages2:

ShowMessage2('Não há quem goste de dor, que a procure e a queira ter, simplesmente porque é dor...');

E em termos de código será igual. Basta agora fazer o search/replace em todo o seu projeto.

Mas e a personalização que falei agora a pouco? Muito bem, o TaskDialog é totalmente personalizável, você pode acrescentar botões, texto expandido, checkbox, radiogroup,…tudo numa única chamada e a parte legal é que obtêm o resultado selecionado sem complicação. Já demonstrei em como substituir o ShowMessage, agora falta os outros dois: MessageBox e MessageDlg que são basicamente duas funções diferentes com o mesmo proposito.

A base para você entender como o TaskDialog funciona com múltiplas opções é a seguinte:

with TTaskDialog.Create(self) do
try
  Caption := 'Confirmação:';
  Title := 'Posso remover o item?';
  Text := 'Posso remover o item selecionado?';
  // CommonButtons é para mostrar os botões padrões que podem ser exibidos:
  // tcbOk, tcbYes, tcbNo, tcbCancel, tcbRetry, tcbClose e retornam ModalResult:
  // mrOK, mrYes, mrNo, mrCancel, mrRetry, mrClose</strong>
  CommonButtons := [tcbYes, tcbNo];
  // MainIcon é para exibir um ícone padrão que pode ser:
  // tdiNone, tdiWarning, tdiError, tdiInformation, tdiShield, tdiQuestion
  MainIcon := tdiQuestion;
  if Execute then
  begin
    // aqui analisamos a opção selecionada com o ModalResult
    if ModalResult = mrYes then
    begin
      // Clicou em Sim
      ShowMessage2('Sim!');
    end;
    if ModalResult = mrNo then
    begin
      // Clicou em Não
      ShowMessage2('Não!');
    end
  end;
finally
  Free;
end;

Vamos a explicação do código acima:

  • A propriedade CommonButtons indica quais botões deverão aparecer, são eles: tcbOk, tcbYes, tcbNo, tcbCancel, tcbRetry, tcbClose que retornarão respectivamente o ModalResult mrOK, mrYes, mrNo, mrCancel, mrRetry, mrClose. Os nomes são autoexplicativos indicando o texto que irá aparecer dentro desses botões e seu ícone, o texto são constantes e não pode ser modificado, se usar tcbYes e tcbNo será respectivamente dois botões “Sim” e “Não”.
  • A propriedade MainIcon pode ser definida com o valor: tdiNone, tdiWarning, tdiError, tdiInformation, tdiShield, tdiQuestion. O nome de cada um deles é autoexplicativo e indicará o tipo de ícone que será exibido ao lado da mensagem.
  • O método Execute (obrigatório) submete a exibição da janela de dialogo e retornará quase sempre verdadeiro para indicar que a mesma foi exibida para o usuário e uma das opções escolhida, mas retorna falso se o usuário optar por abandonar sem selecionar nenhuma das opções.
  • A propriedade ModalResult só é obtida depois que o método Execute for executado, recebendo um desses valores: mrOK, mrYes, mrNo, mrCancel, mrRetry, mrClose em conformidade com a propriedade CommonButtons.

Tendo em mente o que acabei de dizer, terá o suficiente para substituir MessageBox e MessageDlg pelo TaskDialog. Novamente, repita a técnica de refatoração para criar MessageBox2 e/ou MessageDlg2.

Como eu havia dito usar CommonButtons faz com que o texto dentro dos botões sejam inalteráveis, mas é possivel personalizar esses textos criando nossos próprios botões. Para isso funcionar, terá de atribuir a cada um desses botões um ModalResult diferente, por isso a quantidade de botões é limitada. Por exemplo, se eu acrescentar dois botões “Remover” e “Manter” eu terei de dizer que o primeiro terá um ModalResult=mrYes enquanto o segundo escolherei ModalResult=mrNo e somente assim saberei qual foi o botão que o usuário escolheu após o Execute. Importante saber também que caso você opte por usar botões com texto personalizado não poderá usar a propriedade CommonButtons, veja este exemplo simples e fácil de entender:

with TTaskDialog.Create(self) do
try
  Caption := 'Confirmação:';
  Title := 'Posso remover o item?';
  Text := 'Posso remover o item selecionado?';
  // CommonButtons deve estar vazio porque irei constituir meus próprios botões
  CommonButtons := [];
  // Note que cada botão acrescentado, devo ter um ModalResult atribuído para ele
  with TTaskDialogButtonItem(Buttons.Add) do
  begin
    Caption := 'Remover';
    ModalResult := mrYes;
  end;
  with TTaskDialogButtonItem(Buttons.Add) do
  begin
    Caption := 'Manter';
    ModalResult := mrNo;
  end;
  MainIcon := tdiQuestion;
  if Execute then
  begin
    // como cada botão tem seu próprio ModalResult então fica fácil
    // detectar o botão que foi clicado.
    if ModalResult = mrYes then
    begin
      ShowMessage2('Item removido!');
    end;
    if ModalResult = mrNo then
    begin
      ShowMessage2('Item mantido!');
    end;
  end;
finally
  Free;
end;

Novamente, se você estiver desligado, deve se perguntar, porque vou usar um código gigante acima para fazer uma pergunta de sim ou não? Daí novamente vou responder, você não aprendeu a fatorar? Quando tiver que repetir um mesmo código mais de uma vez, crie uma função ou procedimento que torne mais fácil repetir código, veja esse outro exemplo:

function MessageDlg2(
    ACaption:String;
    ATitle:String;
    AText:String;
    AButtonText1:String;
    AButtonText2:String):TModalResult;
begin
  Result:=mrNone;
  if ACaption=emptyStr
    then ACaption:=ExtractFileName(Application.ExeName);
  if ATitle=emptyStr
    then ATitle:='Questão:';
  with TTaskDialog.Create(self) do
  try
    Caption := ACaption;
    Title := ATitle;
    Text := AText;
    // CommonButtons deve estar vazio porque irei constituir meus próprios botões
    CommonButtons := [];
    // Note que cada botão acrescentado, devo ter um ModalResult atribuído para ele
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := AButtonText1;
      ModalResult := mrYes;
    end;
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := AButtonText2;
      ModalResult := mrNo;
    end;
    MainIcon := tdiQuestion;
    if Execute then
    begin
      Result:=ModalResult;
    end;
  finally
    Free;
  end;
end; 

Daí temos uma simpática função para perguntar algo ao usuário:

var
  mrResposta:TModalResult;
begin
  mrResposta:=MessageDlg2(
       'Você tem certeza?',
       'Devo realmente apagar todos os dados?',
       'Se apagar todos os usuários não será mais possível recuperá-los.',
       'Sim, apague tudo',
       'Não, retorne');
    if mrResposta=mrYes then
    begin
      // respondeu "Sim, apague tudo"
    end;
    if mrResposta=mrNo then
    begin
      // respondeu "Não, retorne"
    end;
    if mrResposta=mrNone then
    begin
      // não respondeu nada, abandonou a questão
    end;
end;

Você pode fatorar ou criar funções para obter uma resposta onde há duas opções disponíveis como fiz acima e obterá algo semelhante a isso:

TaskDialog sendo exibida com duas opções de escolha no ambiente Linux

Lembre-se que este foi apenas um exemplo e você pode criar outras funções para a quantidade de opções tantas quanto forem necessárias dum jeito elegante e produtivo.

Botões com RadioButton

Agora vamos incrementar ainda mais nossas opções juntando Botões como foram apresentados nos paragrafos anteriores com RadioButtons que são opções mais flexiveis quando temos uma variade maior de opções:

function QueroEscolher(
    ACaption:String;
    ATitle:String;
    AText:String;
    AOptions:Array of String;
    AIcon:TTaskDialogIcon=tdiNone;
    AProsseguirCaption:String='Prosseguir';
    ADesistirCaption:String='Desistir'):SmallInt;
var
  i:Integer;
  iCount:Cardinal;
  sBotaoCap:String;
  bDefault:Boolean;
  mrResposta:TModalResult;
begin
  Result:=-1;
  iCount:=0;
  if Length(AOptions)=0 then
    Exit;

  bDefault:=false;
  if ACaption=emptyStr
    then ACaption:=ExtractFileName(Application.ExeName);
  if ATitle=emptyStr
    then ATitle:='Escolha uma das opções abaixo:';
  with TTaskDialog.Create(nil) do
  try
    Caption := ACaption;
    Title := ATitle;
    Text := AText;
    // // tdiNone = 0;  tdiWarning = 1;  tdiError = 2;  tdiInformation = 3;  tdiShield = 4; tdiQuestion=5
    MainIcon := AIcon; 
    // CommonButtons deve estar vazio porque irei constituir meus próprios botões
    CommonButtons := [];
    // Caso inclua mais botoões, as opcoes validas na sequencia são:
    // mrYes(0), mrNo(1), mrOk(2), mrCancel(3), mrAbort(4), mrRetry(5),
    // mrIgnore(6), mrAll(7), mrNoToAll(8), mrYesToAll(9), mrClose(10);
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := AProsseguirCaption;
      ModalResult := mrYes;
    end;
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := ADesistirCaption;
      ModalResult := mrNo;
    end;

    // Note que cada botão acrescentado, devo ter um ModalResult atribuído para ele
    for i := Low(AOptions) to High(AOptions) do
    begin
      sBotaoCap:=Trim(AOptions[i]);
      bDefault:=false;
      if (RightStr(sBotaoCap,1)='*') then
      begin
        bDefault:=true;
        sBotaoCap:=LeftStr(sBotaoCap, Length(sBotaoCap)-1);
      end;

      if (sBotaoCap<>emptystr) then
      begin
        with RadioButtons.Add do
        begin
          Caption := sBotaoCap;
          Default:=bDefault;
          Inc(iCount);
        end;
      end;
    end;
    // só executo o TaskDialog se houverem opções disponíveis
    if iCount>0 then
    begin
      if Execute then
      begin
        mrResposta:=ModalResult;
        if mrResposta = mrYes then
           Result:=RadioButton.Index;
      end;
    end;
  finally
    Free;
  end;
end;

Se você notar o código acima, ao invés de usar “with TTaskDialogButtonItem(Buttons.Add)” para acrescentar novos botões usamos “with RadioButtons.Add” e temos ao invés de novos botões, apenas novas opções de radiobutton.

Adicionalmente, fizemos com que nossas escolhas sejam um array de strings então o numero de opções é limitada apenas pela quantidade de elementos que couberem na tela. E fiz um pequeno mimo a mim mesmo, se uma das opções terminar com “*” no texto da opção então a mesma será considerada um radiobutton default.

Veja a forma de usar a função acima:

var
  iResposta:Smallint;
begin
  iResposta:=QueroEscolher(
    'Gerar planilha contendo os dados', // ACaption:String;
    'Gerar planilha contendo os dados sigilosos?', // ATitle:String;
    'Ao prosseguir você concorda com os termos LGPD e manterá os dados dessa planilha '+
    'sob controle absoluto e restringindo o seu acesso apenas a si mesmo ou para uso '+
    'apenas de uma das opções abaixo.', // AText:String;
    [ 'Planilha básica sem dados que possam ligar os dados à pessoa',
      'Planilha básica sem dados que possam ligar os dados à pessoa, apenas enviar por email',
      'Planilha escondendo apenas o CPF e CNPJ*',
      'Planilha com todos os detalhes restritos',
      'Planilha contendo todos os colaboradores que solicitaram esta planilha'
    ]); 

  if iResposta<0 then
    Exit;
  // prosseguindo com a planilha...

E o resultado será algo assim num ambiente Windows:

Ao mostrar como usar botões e radiobuttons numa janela de dialogo do TaskDialog creio que cobrimos 90% do uso prático para ele, mas isso não quer dizer que restam apenas 10% de recursos novos que não detalhei, vou repetir “cobrimos 90% do uso prático” pois ainda há muitas outras formas de uso com inclusão de hyperlinks, checkboxes, rodapés, ícones personalizados… mas que particularmente não considero o feijão com arroz que precisamos para o dia a dia, mas caso queira considerá-los poderá visitar os links que mencionei.

Conclusion

O TaskDialog é uma excelente opção para criação de janelas de dialogo personalizáveis. E neste artigo usamos ele para fatorar janelas de diálogos repetitivos onde as opções de respostas estão previamente definidas como foi feito acima com as funções ShowMessage2 e MessageDlg2.

Você pode criar dentro do seu projeto funções genéricas usando o TaskDialog por baixo do capô como eu demonstrei aqui, assim você evitar trocentas linhas de programação a cada vez que usar o TaskDialog.

Se você está preocupado com o Delphi, o Delphi tem uso bastante similar ao que mencionei aqui, alias creio que o código será o mesmo para Lazarus e Delphi. Minha experiencia com o Lazarus é que há constantes novas no Lazarus que não existem no Delphi, por exemplo o Delphi não possui a constante tdiQuestion para usarmos na propriedade MainIcon.

Escrevam bons códigos e até+