Atenção: esse artigo tem uma palestra complementar. Clique e assista!
Este artigo apresenta os diferentes modelos de desenvolvimento de interfaces de usuário, entre elas o SDI, MDI e TDI, este último tema principal do artigo, onde usamos abas para apresentar os formulários da aplicação, algo muito parecido com o que existe nos navegadores mais modernos, como Chrome e Firefox.
Para que serve
Facilitar a navegação do usuário pelas telas do sistema, usando um modelo mais moderno, intuitivo, usando uma classe pronta, que exige o mínimo de esforço e codificação por parte do desenvolvedor.
Em que situação o tema é útil
Aplicações Delphi Win32, que seguem o modelo Desktop, que possuem uma grande quantidade de formulários, podem tirar grande proveito do modelo TDI em abas. Praticamente qualquer aplicação VCL Win32 pode usar a classe aqui apresentada para passar a adotar o modelo de abas.
Resumo do DevMan
É grande o número de sistemas que estão usando a TDI como layout de navegação, porém, parece que existe um grande mistério por trás disso: como programar esse layout em sistemas que já estão rodando com outro modelo de visualização? A resposta é muita simples e é isto que apresento neste artigo. Você vai conhecer uma classe open source que vai fazer todo o trabalho necessário para que as janelas de sua aplicação sejam exibidas em abas.
A definição mais genérica diz que GUI - Graphical User Interface ou interface gráfica do usuário é a maneira como os dispositivos digitais interagem com o usuário. As aplicações desktop normalmente trabalham com um destes três tipos de GUI, são eles: SDI, MDI e TDI. Antes de aprender como programar o modelo TDI em sua aplicação é importante saber o que é cada um destes layouts.
SDI - Single Document Interface, ou interface de único documento, é um layout que abre as janelas do sistema individualmente. Neste modelo as janelas são exibidas em modal e por isto o usuário não consegue trabalhar em mais de uma janela ao mesmo tempo.
MDI - Multiple Document Interface, ou interface de múltiplos documentos, permite que a aplicação abra diversas janelas filhas, dentro de uma única janela principal. Uma das dificuldades que este modelo de navegação apresenta é a falta de informação sobre as janelas que estão abertas, pois normalmente uma janela fica à frente das outras. Geralmente o programador deve criar um menu específico somente para o tratamento e organização das janelas abertas.
TDI - Tabbed Document Interface, ou Interface de documento tabulada, é a interface popularmente conhecida como navegação por abas. Esse padrão permite que múltiplas janelas filhas sejam exibidas dentro de uma única janela mãe do aplicativo. Este tipo de interface deixa seu aplicativo mais prático além de dar um upgrade no layout. Uma vez que as abas sempre estão à vista do usuário, não é necessário criar um menu específico para a organização das janelas. Um bom exemplo de uso da TDI são os navegadores Firefox e Chorme, no qual a Microsoft se inspirou para adotar a mesma técnica no Internet Explorer 7.
Indiferente do layout utilizado, nem sempre é possível ou conveniente usar um único modelo de interface para todo o sistema. Em aplicações TDI, muitas vezes é necessário que uma janela ou outra seja exibida fora das abas, por isso muitos aplicativos acabam misturando duas interfaces. Como exemplo, posso citar novamente o navegador Firefox que exibe as páginas da web em abas, porém a janela de download é exibida separadamente.
Os tipos de Document Interface são muito utilizados em nosso dia-a-dia, porém, muitas vezes não percebemos sua presença em aplicações ou Sistemas Operacionais. Por exemplo: O modelo SDI (Single Document Interface) utiliza-se do gerenciamento de uma janela por vez. Apesar dos outros conceitos possibilitarem maior flexibilidade, este ainda é o padrão mais utilizado entre os desenvolvedores, por possuir uma facilidade de implementação e gerenciamento muito simples em relação aos outros modelos.
O modelo MDI (Multiple Document Interface) utiliza-se do conceito de abertura de múltiplas janelas simultaneamente, possibilitando a navegação por duas janelas de um mesmo tipo, ou mesmo alterar entre outras janelas abertas. É comumente visto em Sistemas Operacionais.
O modelo TDI (Tabbed Document Interface) caracteriza-se pela abertura de várias janelas dentro de uma janela principal, sendo possível assim como o modelo MDI a abertura de uma mesma janela mais de uma vez. Este modelo surgiu fortemente no navegador Mozilla Firefox e posteriormente foi adotado por programas e outros navegadores como o próprio Internet Explorer, Opera entre muitos outros.
Ainda hoje, não é difícil encontrar softwares que utilizam uma grande tela vazia com um imenso bitmap espalhafatoso com o logo da empresa no fundo; um menu junto com uma barra de botões no topo da janela e mais nada. A cada opção de menu uma janela diferente é chamada. Pode não ser bonito, mas é funcional. Os clientes entendem! Por outro lado, não podemos ignorar vantagens como praticidade e elegância, existentes no modelo de navegação TDI.
A solução
Compartilho neste artigo uma solução muito simples de usar, que ajudará você a usar esta interface: apresento a classe TTDI (não poderia ter outro nome). Esta classe encapsula todo o código necessário para utilização da interface TDI em qualquer aplicativo, esteja ele sendo desenvolvido ou já funcionando há algum tempo. A classe está disponível no arquivo de download deste artigo. O código-fonte está junto e você pode alterar como achar necessário (lembre-se de preservar os créditos e compartilhar novas funcionalidades que por ventura venha a implementar). Na Figura 1 é possível ver que a classe possui apenas três propriedades e apenas dois métodos públicos.
Figura 1. Diagrama da classe TTDI
Detalhando nossa classe TTDI[qsubtitulo]
O primeiro método público de nossa classe tem por responsabilidade abrir o formulário (parâmetro Classe) em uma aba. Se o formulário já estiver aberto em outra aba, uma nova aba será criada se o parâmetro Multi for True. Caso Multi seja False, então a aba já existente será exibida para o usuário:
procedure MostrarFormulario(Classe: TFormClass; Multi: Boolean);
O segundo método publico (Fechar) é auto-explicativo. Quando o parâmetro Todas for True, todas as abas serão fechadas. Caso contrário somente a aba atual será fechada:
procedure Fechar(Todas: Boolean);
FormPadrao é o formulário que será exibido sempre que todas as abas forem fechadas. Atribuir nil a esta propriedade fará com que nenhum formulário seja exibido ao fechar todas as abas:
FormPadrao: TFormClass;
Em MostrarMenuPopup você pode definir se o menu popup com as opções Fechar e Fechar todas será exibido:
MostrarMenuPopup: Boolean;
PageControl é a propriedade que permite o acesso ao PageControl onde as abas (TabSheets) estão sendo exibidas:
PageControl: TPageControl;
Mãos à obra
Então, vamos à prática. Criaremos um projeto de exemplo que simula uma aplicação com um formulário principal, onde estará o menu e onde as abas serão exibidas. Neste formulário adicionarei um Panel alinhado à esquerda, um CheckBox e três SpeedButtons, um para cada opção do sistema (Figura 2).
Figura 2. Tela principal do sistema
Feito isso, vamos criar mais três formulários: o primeiro simulará o Cadastro de clientes, o segundo será denominado como Parâmetros do sistema e o terceiro será o formulário que sempre ficará aberto, como pode ser visto nas Figuras 3, 4 e 5 respectivamente. Não é obrigatório o uso deste terceiro formulário, porém ele pode ser um diferencial do seu sistema. Nele você pode adicionar as informações mais importantes, atualizadas em tempo real. Você pode tratar informações do tipo: atalho para opções mais utilizadas, peças em estoque, vendas do dia e mais uma infinidade de outras coisas de acordo com o seu sistema e a sua criatividade (algo como a Welcome / Start Page do Delphi).
Figura 3. Formulário cadastro de clientes
Figura 4. Formulário parâmetros do sistema
Figura 5. Formulário “padrão” do sistema
Agora veremos como esses formulários serão abertos em abas dentro do formulário principal. Para isso é necessário declarar a unit TDI na sessão uses. Você pode adicionar a unit ao projeto ou simplesmente copiá-la para a Lib do Delphi, ou ainda indicar no Library Path o caminho onde ela se encontra. Veja um exemplo de uso da classe no código na Listagem 1.
Listagem 1. Usando a classe TDI
01 var
02 FormPrincipal: TFormPrincipal;
03 FTDI: TTDI;
04 implementation
05 uses uFormCadastroDeClientes, uFormPadrao, uFormParametrosDoSistema;
06 {$R *.dfm}
07 procedure TFormPrincipal.FormCreate(Sender: TObject);
08 begin
09 FTDI := TTDI.Create(Self, TFormPadrao);
10 FTDI.MostrarMenuPopup := True;
11 end;
12 procedure TFormPrincipal.BitBtn1Click(Sender: TObject);
13 begin
14 FTDI.MostrarFormulario(TFormCadastroDeClientes,
CBMultinstancia.Checked);
15 end;
16 procedure TFormPrincipal.BitBtn2Click(Sender: TObject);
17 begin
18 FTDI.MostrarFormulario(TFormParametrosDoSistema,
CBMultinstancia.Checked);
19 end;
20 procedure TFormPrincipal.SpeedButton1Click(Sender: TObject);
21 begin
22 Close;
23 end;
24 procedure TFormPrincipal.FormDestroy(Sender: TObject);
25 begin
26 FreeAndNil(FTDI);
27 end;
Este é todo o código que você precisa escrever para que sua aplicação passe a usar o layout TDI, mostrando os formulários em abas. Como podemos ver na Linha 03 é declarada a variável FTDI, do tipo TTDI para fazer o controle das abas. Na Linha 05 temos a adição dos formulários auxiliares ao nosso formulário principal. Note que na Linha 09, a variável FDTI é iniciada. No primeiro parâmetro passamos o formulário onde as abas devem ser exibidas e no segundo parâmetro passamos o formulário que será exibido automaticamente quando todos os outros forem fechados. Se preferir pode passar nil no segundo parâmetro. Nas Linhas 14 e 18, o método MostrarFormulario é chamado para exibir respectivamente os formulários de cadastro de cliente e de parâmetros do sistema. Este é o momento em que os formulários são exibidos em abas. Na Linha 26 após fechar o formulário principal, tiramos a variável FTDI da memória. Uma vez que o código foi compreendido, compile o projeto e veja o resultado. Sua aplicação já estará como nas Figuras 6, 7, e 8.
Figura 6. Imagem do sistema após ser executado
Figura 7. Exibindo várias abas
Figura 8. Menu popup
Não se esqueça de que não é necessário criar instâncias dos formulários manualmente para depois usá-los nas abas, pois o método MostrarFormulario irá fazer isso por você. Por tanto, você pode retirá-los da sessão Auto-create forms que está no menu Project>Options, aba Forms. Retire todos, menos o formulário principal da aplicação.
Um pouco mais
Com pouco tempo você implementou um layout moderno em sua aplicação. Por que não aproveitar o tempo que sobrou para deixá-lo mais moderno ainda? Você verá agora, que a classe TTDI possibilita a você mais do que simplesmente exibir as janelas em abas. Através de uma interface chamada IVisualizador, a classe TTDI pode exibir uma lista das janelas que já estão abertas. Esta lista pode ser exibida em qualquer formato: seja uma lista de strings com o nome de cada aba aberta ou algo mais complexo como uma lista de imagens de cada formulário aberto em abas. Veja abaixo o método da classe TTDI que deve ser chamado para que a lista de abas seja exibida.
procedure VisualizarAbas(Visualizador: IVisualizador; NaoExibir: TFormClass);
Este método faz exatamente o que seu nome sugere: ele exibe uma lista de todos os formulários abertos em abas. O primeiro parâmetro, Visualizador, deve estar referenciando um objeto cuja classe implemente a interface IVisualizador (logo vamos falar mais sobre esta interface). No segundo parâmetro, NaoExibir, você pode informar a classe de algum formulário que não deve ser exibido na lista.
No método VisualizarAbas, podemos ver que a classe TTDI está utilizando o padrão de projeto Strategy também conhecido como Policy. Este padrão define uma família de algoritmos através de uma interface a fim de torná-los intercambiáveis. No caso da classe TTDI, a família de algoritmos é qualquer objeto que implemente a interface IVisualizador. Com isto o programador pode trocar o comportamento de um método em tempo de execução. O padrão strategy consegue isto através de fundamentos como o polimorfismo e de princípios de orientação a objetos como “programar para uma interface e não para uma implementação”.
Eu disse que a lista pode ser exibida em qualquer formato, porque quem define a maneira como a lista será exibida é o objeto passado no parâmetro Visualizador, sendo assim você pode trocar a maneira como a lista será exibida em tempo de execução, basta trocar o objeto que será passado no primeiro parâmetro do método VisualizarAbas. Está é uma prática muito comum em programação orientada a objetos.
Junto ao arquivo de download, além do projeto de exemplo e do arquivo TDI.pas, você vai encontrar também o arquivo VisualizaImagensDasGuiasAbertas.pas. Dentro deste arquivo existe a classe TVisualizaImagensDasGuiasAbertas. Você pode criar um algoritmo próprio para exibir as janelas que já estão abertas: tudo que você precisa para isto é criar uma nova classe que implemente a interface IVisualizador, detalhada na Listagem 2.
Listagem 2. Interface IVisualizador
IVisualizador = interface
['{07EF861E-5B9F-4534-8D6B-3A62BB8C4F80}']
procedure ListarFormulario(FormularioAberto: TForm);
end;
Veja mais detalhes sobre a interface IVisualizador no próprio código-fonte dentro do arquivo TDI.pas.
Implementando o novo comportamento
Neste exemplo vamos adicionar mais um formulário ao projeto criado no tópico. Este novo formulário terá o objetivo de exibir a lista de formulários abertos. Crie um novo formulário através do menu File>New>Form, adicione um TScrollBox, que pode ser encontrado na paleta Additional e salve esta nova unit com o nome uFormGuiasAbertas.pas. Configure as propriedades do formulário e do ScrollBox de acordo com a Tabela 1.
Objeto |
Propriedade |
Valor |
TForm |
Name |
FormGuiasAbertas |
Caption |
Formulário abertos |
|
Color |
clWhite |
|
TScrollBox |
Align |
alClient |
HorzScrollBar.Smooth |
True |
|
VertScrollBar.Smooth |
True |
Tabela 1. Configurando o formulário de visualização
Após configurar as propriedades codifique este novo formulário como na Listagem 3, não se esqueça de declarar a unit VisualizaImagensDasGuiasAbertas.pas na sessão uses do formulário.
Listagem 3. Pedindo uma lista de janelas abertas
1 Private
2 Visualizador: IVisualizador;
3 Public
{ Public declarations }
4 end;
5 Var
6 FormGuiasAbertas: TFormGuiasAbertas;
7 Implementation
8 uses DateUtils, Types, VisualizaImagensDasGuiasAbertas, uFormPrincipal;
9 {$R *.dfm}
10 procedure TFormGuiasAbertas.FormShow(Sender: TObject);
11 Begin
12 Visualizador := TVisualizaImagensDasGuiasAbertas.Create(ScrollBox1, FTDI);
13 FTDI.VisualizarAbas(Visualizador, TFormGuiasAbertas);
14 end;
15 procedure TFormGuiasAbertas.FormClose(Sender: TObject;
16 var Action: TCloseAction);
17 Begin
18 Visualizador := nil;
19 end;
20 end.
Nenhuma linha a mais é necessária para que uma lista com a imagem e o Caption de cada formulário aberto em aba seja exibida dentro do ScrollBox. Na linha 2, declaramos a variável que vai receber uma instância da classe TVisualizaImagensDasGuiasAbertas. Note que declaramos o tipo da variável como IVisualizador porque estamos seguindo o principio de orientação a objetos que nos diz para sempre programar para uma interface. Na linha 12, inicializamos a variável Visualizador. Nossa variável vai receber uma instância da classe TVisualizaImagensDasGuiasAbertas que pode ser encontrada junto ao arquivo de download deste artigo. Na linha 13, chamamos o método VisualizarAbas da classe TTDI. A variável FTDI que você vê no início da linha 13 é a mesma variável declarada no FormPrincipal que pode ser vista na linha 3 da Listagem 1, por isto você também deve declarar a unit uFormPrincipal no uses do formulário (veja na linha 8 da Listagem 3). Observe que, ao pedirmos para a classe TDI exibir a lista de formulário aberto, passamos no segundo parâmetro a classe do formulário de visualização: fazemos isto porque não queremos que o formulário de visualização apareça na lista de formulários abertos. Por fim na linha 18, a referência feita pela variável Visualizador, é anulada.
Você não deve usar Free ou FreeAndNil para liberar da memória objetos para o qual as variáveis de interfaces estão apontando. Ao invés disto, apenas atribua o valor nil para estas variáveis, desta maneira o Delphi irá identificar que o objeto para o qual a variável estava apontando não é mais usado e automaticamente irá liberar o espaço utilizado por ele.
Antes de compilar e executar para ver como a lista é exibida, é necessário fazer a chamada do formulário FormGuiasAbertas no formulário principal da aplicação. Adicione outro SpeedButton ao formulário FormPrincipal e configure-o para que fique como na Figura 9.
Figura 9. Adicionado o botão para chamar o FormGuiasAbertas
Veja na Listagem 4 o código do evento OnClick deste novo botão. Ao clicá-lo, nós queremos que o formulário FormGuiasAbertas seja exibido então, chamamos o método MostrarFormulario da classe TTDI para que o formulário seja aberto em uma aba. Observe que foi definido o valor fixo False para o segundo parâmetro do método ao invés de usarmos o valor da propriedade Checked do objeto CBMultinstancia, como foi feito nas linhas 14 e 18 da Listagem 1. Isto acontece porque não é necessário abrir mais de uma vez a janela de visualização.
Listagem 4. Chamando o formulário FormGuiasAbertas
procedure TFormPrincipal.SpeedButton2Click(Sender: TObject);
begin
FTDI.MostrarFormulario(TFormGuiasAbertas, False);
end;
Se não restam mais dúvidas, compile e execute o programa para ver o resultado. Quando o programa abrir, abra a guia com o “Cadastro de clientes” e o “Parâmetro da aplicação”, só depois peça para abrir o formulário de visualização. Seguindo estes passos, sua aplicação deve estar como é apresentado na Figura 10.
Figura 10. Formulário de visualização das abas abertas
Graças à implementação da classe TVisualizaImagensDasGuiasAbertas o usuário pode clicar em uma destas imagens para ser levado diretamente à aba com o formulário correspondente ao da imagem clicada. Lembre-se que você pode criar outros algoritmos mais rápidos para exibir a lista dos formulários abertos: basta criar uma classe que implemente a interface IVisualizador.
Como a classe TVisualizaImagensDasGuiasAbertas funciona
Não custa nada dar uma espiada em como a classe TVisualizaImagensDasGuiasAbertas consegue exibir estas imagens e ainda por cima levar o usuário até a aba correspondente a imagem quando ela é clicada. Como isto é possível, se não existe uma forte ligação entre a classe TTDI e a classe TVisualizaImagensDasGuiasAbertas? Veja na Figura 11 o diagrama desta classe.
Figura 11. A classe TVisualizaImagensDasGuiasAbertas
TVisualizaImagensDasGuiasAbertas implementa apenas o código ListarFormulario(TForm), que é obrigatório para qualquer classe que implemente a interface IVisualizador. Este método será chamado tantas vezes quanto são os formulários abertos e quem se responsabiliza por fazer estas chamadas é o método VisualizarAbas(IVisualizador, TFormClass) da classe TTDI (veja a Listagem 5). Toda vez que este método for chamado, será passado uma refêrencia ao formulário que está aberto pelo parâmetro FormularioAberto. Além deste método, obrigatório, a classe tem o seu construtor que recebe um TWinControl onde serão exibidas as imagens dos formulários, além de receber também uma refêrencia ao objeto TTDI. Este Segundo parâmetro do construtor não é obrigatório, mas se ele for definido será criado um evento OnClick para cada imagem exibida que levará o usuário a aba correspondente ao formulário exibido na imagem.
Listagem 5. O método VisualizarAbas da classe TTDI
1 procedure TTDI.VisualizarAbas(Visualizador: IVisualizador;
2 NaoExibir: TFormClass);
3 Var
4 i: Integer;
5 Form: TForm;
6 Begin
7 for i := 0 to PageControl.PageCount - 1 do
8 Begin
9 Form := Formulario(i);
10
11 if Form.ClassType <> NaoExibir then
12 Visualizador.ListarFormulario(Form);
13 end;
14 end;
Repare na linha 1 da Listagem 5 que o parâmetro Visualizador é do tipo de uma interface, isto significa que o método VisualizarAbas da classe TTDI não sabe qual tipo de lista está sendo formada, pois o parâmetro Visualizador pode estar apontando tanto para a classe TVisualizaImagensDasGuiasAbertas como para outra classe que você mesmo tenha criado. Tudo que este método sabe é que deve chamar o método ListarFormulario da interface IVisualizador para cada formulário que estiver aberto em uma aba. Este é o padrão Strategy é ação.
Para mais informações sobre como a classe TVisualizaImagensDasGuiasAbertas funciona, veja os comentários no próprio código-fonte de classe.
Conclusão
Simples, não? Agora você pode exibir as janelas do seu programa em abas, com um esforço conceitualmente nulo. Para aqueles que quiserem entender melhor como a classe funciona o código está bem comentado. Sua aplicação está agora pronta para ter um layout mais profissional, adotando um padrão que vem ganhando popularidade no mercado.