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:
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
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:
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é+