Interface do Usuário
Interface do Usuário
uniGUI integra-se convenientemente ao designer VCL do RAD Studio para aproveitar o conhecimento dos desenvolvedores atuais, mas o designer é incapaz de renderizar a experiência exata do usuário da aplicação real em execução em um navegador web.
Como já foi mencionado antes, componentes VCL são incompatíveis com uniGUI, mas os componentes uniGUI mantêm várias propriedades VCL, o que proporciona melhor compatibilidade ao migrar aplicações VCL para aplicações uniGUI. Entretanto, os componentes uniGUI renderizam componentes ExtJS que são especificamente projetados e otimizados para a web. Dito isso, mesmo componentes uniGUI padrão como TUniPanel aproveitam recursos da web, enquanto outros componentes não têm equivalente na VCL. Componentes uniGUI tornam-se componentes Sencha Ext JS executando no navegador do cliente, e a Sencha está continuamente melhorando seus componentes e criando novos componentes.
A melhor prática ao criar a interface do usuário com uniGUI é configurar os componentes para a web e usar os melhores componentes otimizados para a web.
Vamos mostrar como aplicar essa melhor prática fazendo uma comparação entre alinhamento VCL e alinhamento no lado do cliente, e aprendendo como usar TUniFieldSet e TUniFieldContainer.
Alinhamento VCL vs alinhamento web
O alinhamento VCL ocorre no lado do servidor, enquanto o alinhamento web é executado imediatamente no lado do cliente. Se o desenvolvedor usa um controle TPanel com um TSplitter para ajustar a largura do painel, arrastar o TSplitter criará uma sequência de requisições ao servidor que enviará atualizações ao cliente para renderizar as mudanças. Isso é custoso e torna a interface do usuário menos responsiva.
VCL
Algumas das propriedades dos controles VCL são:
Altura
Largura
Topo
Esquerda
Align - alNone, alTop, alBottom, alLeft, alRight, alClient
O Windows, e a VCL, que é uma encapsulação orientada a objetos e baseada em componentes dos objetos visuais do Windows, são ambos baseados em coordenadas físicas da tela (é a razão dos nossos problemas atuais com displays de alta resolução usando DPI muito maiores).
A World Wide Web precisa lidar com dispositivos cliente muito diferentes, e ela é baseada em posições relativas.
uniGUI mantém as mesmas propriedades VCL, mas também permite substituir o comportamento VCL e aproveitar as propriedades web mais poderosas e flexíveis.
Web
Alinhamento em Contêineres
Algumas das propriedades Web são:
AlignmentControl - uniAlignmentClient, uniAlignmentServer
Layout - absolute, accordion, anchor, auto, border, fit, form, hbox, vbox, table, column
LayoutAttribs
Align - top, middle, bottom, stretch, stretchmax
Colunas
Pack - start, center, end
Padding
LayoutConfig
Anchor
BodyPadding
ColSpan
ColumnWidth
DockWhenAligned
Flex
Altura
IgnorePosition
Margin
Padding
Região - north, south, east, west, center (equivalente a alTop, alBottom, alRight, alLeft, alClient)
RowSpan
Split - Para habilitar um splitter automático no lado do cliente
Largura

Na pasta Demos há vários projetos que mostram como usar as propriedades anteriores:
Alinhamento no Lado do Cliente - Dock e Align
Alinhamento no Lado do Cliente - Layout Accordion
Alinhamento no Lado do Cliente - Layout Anchor
Alinhamento no Lado do Cliente - Layout Border
Alinhamento no Lado do Cliente - Layout Column
Alinhamento no Lado do Cliente - Layout Fit
Alinhamento no Lado do Cliente - Layout Form
Alinhamento no Lado do Cliente - Layout HBox
Alinhamento no Lado do Cliente - Layout Percentage
Alinhamento no Lado do Cliente - Layout VBox
Alinhamento no Lado do Cliente - Layout Table
Alinhamento no Lado do Cliente - Layout Table Span
Alinhamento no Lado do Cliente - Demonstração de Recursos
Painéis
Painéis UniGUI também são recolhíveis. As propriedades correspondentes são:
Collapsed
CollapseDirection - cdBottom, cdDefault, cdLeft, cdRight, cdTop
Collapsible
A demo Collapsible Panels mostra vários painéis recolhíveis.

Esta demo começa com alguns painéis recolhidos e todos os quatro podem ser recolhidos ou expandidos usando os botões correspondentes. Você notará que cada requisição de recolher/expandir requer uma ida ao servidor para renderizar o novo layout. Tente alterar esse comportamento mudando o AlignmentControl do formulário para uniAlignmentClient e configurando os controles usando as propriedades web descritas anteriormente. Confirme que a nova aplicação executa mudanças de layout muito mais rápido (não há mais necessidade de pedir ao servidor uma nova renderização).
FieldSets
TUniFieldSet
O controle uniGUI TUniFieldSet contém campos (controles/editors uniGUI) que devem ser organizados em uma coluna. Este contêiner renderizará cada campo de acordo com propriedades comuns:
FieldLabel
FieldLabelAlign - alLeft, alRight, alTop
FieldLabelFont
FieldLabelSeparator - padrão ':'
FieldLabelWidth
Entre os controles uniGUI que podem ser incluídos em um TUniFieldSet está o TUniFieldContainer, que permite criar formulários hierárquicos como:

TUniFieldContainer
O contêiner organiza os campos de acordo com o layout web solicitado. No exemplo anterior, FieldContainer3 está usando layout table com 3 colunas.

O TUniFieldContainer renderiza os campos na ordem em que foram criados. Se essa ordem estiver ou se tornar incorreta, a propriedade CreateOrder deve ser usada para indicar a ordem desejada. O valor padrão é zero, significando que eles devem ser os últimos campos. A ordem desejada começa em um, e os campos serão salvos no arquivo .DFM nessa ordem.
Separar Interface do Usuário da Lógica de Negócio
Delphi é um ambiente RAD que incentiva a velocidade de desenvolvimento. É fácil criar uma pequena aplicação apenas arrastando alguns componentes para um formulário, alterando algumas propriedades, adicionando alguns event handlers e implementando o código real que era o objetivo original da aplicação. Em alguns minutos, você pode ter uma aplicação funcionando. É verdade; o RAD Studio torna isso possível. Mas também é verdade que, na maioria das vezes, essa aplicação não será uma boa base para um projeto maior. Mesclar a interface do usuário com a lógica de negócio, adicionar controles visuais e componentes de acesso a dados, ter todo tipo de event handlers na mesma unit é uma boa receita para falha.
Existem muitas soluções para desconectar a camada de apresentação da lógica de negócio. Todo mundo conhece MVC (Model-View-Controller), MVP (Model-View-Presenter), MVVM (Model-View-ViewModel), e padrões de projeto similares. Enquanto alguns desses padrões são genéricos e podem ser implementados em várias linguagens, ambientes de desenvolvimento e frameworks de suporte, alguns deles são melhores para cenários específicos. Por exemplo, MVVM foi projetado especificamente para tirar vantagem do WPF (Windows Presentation Foundation), XAML, e programação orientada a eventos.
Apesar do ambiente RAD do Delphi, várias de suas funcionalidades permitem alcançar o objetivo anterior, sem necessariamente seguir padrões estritos.
Use interfaces, não forms
Vamos começar mostrando como aplicar uma das melhores práticas mais importantes: programar contra interfaces, não contra classes concretas.
Nosso programa poderia rodar como uma aplicação desktop ou como uma aplicação Web, mas nossa lógica de negócio exigirá interação do usuário. Se precisamos pedir ao usuário alguma informação, precisaremos usar algum artefato visual, mas isso não significa que nossa lógica de negócio precise conhecer detalhes desnecessários sobre ele. Melhor ainda, deveria ser possível escrever a lógica de negócio sem ter uma implementação concreta do artefato (formulário, diálogo de mensagem, seja o que for).
Será melhor explicar este princípio com um exemplo simples.

Exemplo de unit de formulário VCL:
A lógica de negócio só precisa acessar uma função GetOrModifySomeText que devolverá true se o texto passado como parâmetro foi modificado. Não precisa saber como isso é feito. Se a única forma de usar a função for fazer uma referência explícita à unit que implementa a lógica de negócio, estaremos adicionando uma dependência a um form VCL e, pior ainda, a uma implementação particular dele.
O mesmo formulário, desta vez implementado como um form livre uniGUI, requer um código similar.
A única diferença aqui é que não precisamos liberar explicitamente o form uniGUI.
Agora, e quanto a poder escrever a lógica de negócio sem ter uma implementação concreta do form, ou melhor ainda, como direcionar tanto VCL quanto uniGUI com o mesmo código?
Vamos fazer algum refatoramento.
Extrair uma interface que exponha o comportamento do form
Modificar ambos os forms para implementar a interface
Não referencie os forms, mas a interface
Uma vez que o form foi criado, ele pode ser passado como a interface que implementa. Observe que adicionamos _ShowModal à interface porque ShowModal é diferente para VCL e uniGUI.
Não acesse forms, exceto para criar e liberar instâncias
Um form Delphi é uma "view" ou uma das possíveis implementações da camada de apresentação. Deve sempre implementar uma ou várias interfaces para esclarecer sua finalidade. Qualquer acesso ao form deve ser feito usando uma das interfaces que ele implementa, ocultando qualquer detalhe de implementação e evitando erros futuros ao modificar recursos internos.
Acessar forms através de interfaces evita quebrar o código que usa o form quando o form muda. Também abre a possibilidade de criar diferentes forms/views que implementem a mesma interface. Por exemplo:
A aplicação poderia compartilhar grande parte da lógica de negócio enquanto mira tanto o desktop Delphi VCL quanto o Delphi uniGUI (também com aparência de desktop).
Uma aplicação web construída com uniGUI poderia fornecer duas interfaces de usuário diferentes, uma para usuários de desktop (com mouse e teclado) e outra para dispositivos touch (telefones e tablets).
Nunca introduza código de lógica de negócio em forms, aproveite as funcionalidades do Delphi
Cada linha de código que expressa lógica de negócio e está embutida em um form cria uma dependência desnecessária e impede a criação de novas views. Uma aplicação web típica fornece acesso a um servidor de banco de dados através de uma camada de lógica de negócio. Os objetos Delphi equivalentes são forms, data modules, classes de negócio opcionais e as conexões de banco de dados.
Vamos analisar como esses objetos Delphi se relacionam com os componentes do padrão MVC.

Uma implementação Delphi de MVC é algo como isto:
A View é um TForm, TUniForm, TUnimForm.
É criada em tempo de design e renderizada em tempo de execução.
Algumas propriedades podem ser atualizadas através de sua interface (ainda um bom comportamento)
A maioria das atualizações será automaticamente propagada através dos componentes de banco de dados (conectados durante a criação)
Qualquer ação do usuário pode disparar atualizações relacionadas ao banco de dados (perfeito), enquanto outras podem disparar ações (também bom). Outro comportamento aceitável seria executar ações necessárias/esperadas como criar seu próprio data module e liberá-lo.
O Controller geralmente é o data module usado pelo form, e na maioria das vezes ele contém parte ou todo o Model (pelo menos, os componentes de banco de dados).
Em uma aplicação pequena, Controller e Model serão implementados no MainModule.
Aplicações maiores podem usar um data module para alguns forms, além do MainModule.
O padrão Model-View-Presenter sugere algo similar.

Neste padrão, o Presenter é o mecanismo implementado em VCL ou uniGUI para tratar a "comunicação" entre o Model (data module e classes de negócio) e a View (form). Vale mencionar que esta View é passiva (ou o mais passiva possível), o que significa que não incluirá nenhuma lógica de negócio.
Na próxima seção mostraremos como construir uma aplicação que atinja ambas as plataformas uniGUI (desktop e touch), mas é importante listar as funcionalidades do Delphi que a aplicação usará.
Controles de banco de dados e seus eventos
Como o objetivo é manter o código de lógica de negócio fora da View, nunca usaremos componentes de acesso a dados em um form. Em vez disso, todos esses componentes serão hospedados por data modules. Em uma aplicação VCL, esse data module será criado automaticamente e atribuído à variável global. No uniGUI a abordagem é diferente, pois o MainModule será acessível através da função uniMainModule. Em ambos os casos, qualquer form pode vincular seus controles de acesso a dados em tempo de design (às fontes de dados e datasets correspondentes).
Seguindo o princípio de consumir recursos quando necessários, sempre sob demanda, cada form que exigir acesso ao banco de dados deve anunciar/solicitar ao data module a ativação/desativação de seus recursos (isto é, abrir e fechar datasets). Essas ações devem ser feitas em OnCreate e OnDestroy.
Em aplicações maiores, pode ser necessário criar e destruir quaisquer data modules adicionais usados pelo form.
Em qualquer caso, ao hospedar os controles de acesso a dados no data module, qualquer evento será implementado no mesmo lugar (ou poderá delegar para alguma classe de negócio definida fora do data module).
Seguindo essas práticas, nenhuma lógica de negócio é adicionada à View. Como mencionaremos mais adiante neste capítulo, é ainda possível injetar essas dependências mínimas.
Solicitando ações da View sem escrever código (independente de plataforma)
E quanto a usar um menu (MainMenu ou PopupMenu), pressionar um botão para solicitar uma ação global ou uma ação focada em algum registro selecionado de um dataset?
Em uma aplicação centrada em dados, a maioria das ações solicitadas afeta os registros ativos ou alguma seleção de registros. Outras ações são globais e podem ser solicitadas em vários pontos da interface do usuário.
Delphi suporta actions e action lists há muito tempo. Cada botão em uma barra de ferramentas pode ser vinculado a uma action. Itens de menu de qualquer menu também podem ser vinculados a actions. Outros botões compartilham essa capacidade. Se a action list for hospedada pelo mesmo data module, sempre que o usuário disparar uma action, o método Execute da action poderá consultar o contexto (por exemplo, o registro selecionado de um dataset) e implementar a lógica de negócio solicitada.
Claro, algumas ações podem requerer interação com o usuário através da mesma interface de onde vieram. Enquanto usar interfaces permite ao desenvolvedor acessar uma View sem saber como ela é implementada ou qual plataforma está sendo alvo, nesse caso precisaremos instanciar a View para a plataforma atual.
Vamos ver duas possíveis soluções para a pergunta anterior.
Criar uma interface como fachada para serviços dependentes da plataforma
Quando a aplicação VCL ou a sessão uniGUI inicia, qual for a plataforma atual pode ser salva e usada mais tarde para instanciar as Views corretas. Claro, a aplicação VCL sempre será VCL, mas uma aplicação uniGUI pode estar direcionada a duas plataformas: desktop e touch. Se a sessão do usuário começou em um dispositivo touch, qualquer interação subsequente dependerá de forms e mensagens para touch.
A solução mais fácil para projetos pequenos é definir uma interface para todos os "serviços" (forms e mensagens) requeridos pela lógica de negócio, implementá-los para ambos os alvos e instanciar o correto de acordo com a sessão do usuário. Depois disso, toda vez que um form for necessário, a implementação concreta, disponível através da interface, será usada.
Use Injeção de Dependência para registrar múltiplas implementações da mesma interface de form
Projetos grandes podem ter centenas ou até milhares de forms, tornando a solução anterior tediosa, demorada e propensa a erros. A solução correta é deixar essa tarefa para um framework de Injeção de Dependência como Spring4D.
Isso está fora do escopo desta documentação, mas iremos mostrar como modificar nossa demo para fazê-lo.