Usando Cards: Modernizando o Visual de sua aplicação
Introdução
O TDBGrid é o herói cansado do ecossistema Pascal. Ele nos serviu bem por décadas, mas
sejamos sinceros: ele tem aquele "cheiro" de aplicação dos anos 90, com cara de planilha de Excel
antiga. Em um mundo onde o usuário está acostumado com interfaces fluidas e intuitivas, o DBGrid pode
parecer burocrático e confuso quando a densidade de informação é alta.
Os Cards (cartões) são a evolução natural. Eles agrupam informações relacionadas em blocos visuais únicos, facilitando a leitura e permitindo designs que se adaptam muito melhor a diferentes resoluções de tela.
Nota técnica: Para este guia, utilizaremos nomes de componentes genéricos como
TQuery e TMemTable. Entretanto, o princípio de migração e a lógica de
construção são os mesmos para qualquer outra suíte de acesso, seja o SQLdb (nativo do
Lazarus), Zeos (popular no Lazarus e Delphi), FireDAC (exclusivo do
Delphi e limitado conforme a licença), UniDAC ou qualquer outra que você utilize.
Nosso ponto de partida: um DBGrid funcional, mas visualmente saturado.
Note que na imagem acima o TDBGrid está sendo levado ao seu limite técnico: estamos usando
várias linhas de dados em uma única linha de grid, imagens, checkboxes e outros truques de modernização.
Chegar nesse resultado exige uma montanha de código nos eventos de desenho (como o
OnDrawColumnCell). É justamente por isso que muitos programadores evitam usar todo o
potencial do Grid: a manutenção torna-se um pesadelo. No decorrer deste guia, você verá como os Cards
simplificam essa programação e entregam um aspecto muito mais moderno com uma fração do esforço.
A Abordagem: Frames + FlowPanel
Muitos desenvolvedores tentam criar "Cards" desenhando componentes diretamente em um painel comum. Fuja disso! Fazer desta forma é assinar um contrato de pesadelo de manutenção. Se você precisar mudar uma cor de fonte ou adicionar um botão em 50 cards criados manualmente, você vai se arrepender amargamente depois de 6 meses.
O TFrame: O Coração da Manutenibilidade
Se você nunca usou um TFrame, pense nele como um "mini formulário" onde você pode
ancorá-lo (termo técnico para "ser colado") dentro de outros formulários ou painéis
(TPanel). Diferente de um TForm, o Frame não é uma janela independente; é um
contêiner de componentes que você desenha uma vez e reutiliza quantas vezes quiser e onde quiser.
Como criar um Frame no Lazarus:
- Vá ao menu superior: File > New...
- Na árvore de opções, escolha Module e selecione Frame.
- O Lazarus abrirá uma área de design vazia. Salve-a como
ufrItemCard.pase dê o nome à classe deTfrItemCard. - Agora, desenhe o seu card lá dentro: coloque Labels para o título, imagens para o produto e as ações.
Design Responsivo no Card: Ancoragem Avançada
Ao desenhar seu Card, saber usar alinhamento e ancoragem é vital. No Lazarus, temos a propriedade básica
Anchors (akTop, akLeft, etc.), mas para um layout verdadeiramente profissional e dinâmico,
você deve usar o Anchor Editor.
O Anchor Editor permite amarrar componentes entre si, criando layouts inteligentes.
Acesse-o em View > Anchor Editor. Ele vai muito além das âncoras simples: essa
ferramenta permite que você amarre um componente a outro objeto de referência. Por exemplo: você pode
configurar para que o TEdit edtDescricao fique sempre ancorado à borda inferior e à direita
do Label lblDescricao, mantendo uma distância fixa e, mesmo em tempo de design, se você arrastar o
TLabel, o TEdit virá junto.
Se abaixo do TEdit de descrição tiver um Label para "Saldo em estoque" alinhado com o edtDescricao, esse será levado junto quando você arrastar o TLabel. Fica como no antigo jogo dos Lemmings: ao arrastar um, você arrasta todo mundo que está amarrado na fila! Além disso, se um componente crescer de altura em tempo de execução, os que estão abaixo se ajustam automaticamente. No início, você achará esse tipo de alinhamento mais trabalhoso de lidar; no entanto, com o passar do tempo, verá que ele economiza tempo na manutenção, deixa o design mais fluido e também evita que os componentes se sobreponham, mantendo a integridade visual do card independentemente do tamanho do texto carregado. Veja:
Dica Especial: Revelamento Progressivo (Progressive Disclosure)
Às vezes, um card precisa mostrar muita informação, mas você não quer que ele ocupe a tela inteira por padrão. A técnica para resolver isso pode envolver várias estratégias, dependendo do objetivo visual:
1. Alternância de Conteúdo com TPageControl
Se você deseja trocar uma informação por outra no mesmo espaço (ex: alternar entre "Dados do Produto" e
"Histórico de Vendas"), use um TPageControl sem abas (ShowTabs := False).
Isso desliga uma informação e "liga" outra no mesmo card apenas mudando a ActivePageIndex
via código.
2. Expansão Vertical (Efeito Accordion)
Se a ideia é mostrar mais informações rolando para baixo, a técnica ideal é empilhar painéis
(TPanel) sem bordas dentro do Frame, todos com Align := alTop. Mantenha os
painéis de detalhes com Visible := False inicialmente. Quando o usuário clicar em um botão
de "Ver mais" (ícone ▼), alterne a visibilidade desses painéis.
Como o Frame e o FlowPanel externo estão configurados com AutoSize := True, sua aplicação
irá
reorganizar todos os outros cards automaticamente quando um deles "esticar" para revelar os detalhes. É
um efeito extremamente moderno e funcional.
O TFlowPanel: O Organizador Automático
O TFlowPanel é um componente de layout "inteligente". Ao contrário de um
TPanel comum, onde você solta um componente e ele fica numa posição fixa, o FlowPanel
organiza os seus filhos automaticamente em fila. Há uma propriedade nele chamada FlowStyle
que permite que você defina a direção da fila, seja horizontal ou vertical, o padrão é horizontal
fsLeftRightTopBottom. Geralmente, você vai usar os seguintes parametros definidos assim:
AutoSize := True: Permite o ajuste automático do tamanho do controle de acordo com o seu conteúdo. Por exemplo, um card pode aumentar ou diminuir para acomodar uma descrição mais longa ou mais curta. Por padrão, esta propriedade éFalse.FlowStyle := fsLeftRightTopBottom: Define a direção do fluxo usada para posicionar os controles no painel. O valor padrão organiza os itens da esquerda para a direita e de cima para baixo.FlowLayout := tlTop: Define o alinhamento para a coordenada de "não-fluxo" (o eixo perpendicular à direção do fluxo). Por exemplo, em um fluxo horizontal,tlTopalinha os itens pelo topo/esquerda;tlCentercentraliza etlBottomalinha pela base/direita. O valor padrão étlTop.Wrap := True: Determina se os componentes filhos são automaticamente "quebrados" para uma nova linha quando necessário. O valor padrão para esta propriedade noTFlowPaneléTrue.
Outras propriedades importantes são Padding e Spacing, que definem o
espaçamento
entre os cards e entre os componentes dentro do card. Por exemplo, se você quiser que os cards fiquem
alinhados à direita, você pode usar a propriedade Align := alRight.
Tem também propriedades puramente estéticas e já conhecidas como BevelEdges,
BevelInner,
BevelOuter, BevelKind, BevelWidth, BorderWidth,
BorderStyle, Color, Width e
Height. É importante notar que as propriedades Width e Height
são definidas automaticamente pelo FlowPanel quando AutoSize := True.
Como configurar o FlowPanel:
- Localize-o na paleta de componentes (normalmente em Standard ou Additional).
- Coloque-o no formulário e defina a propriedade
AlignparaalTop. - O Pulo do Gato: Coloque o FlowPanel dentro de um
TScrollBox(alClient) e definaFlowPanel.AutoSize := True. Isso garantirá que barras de rolagem apareçam automaticamente quando a lista de cards crescer!
Ações no Card: Do Ícone ao Menu
Uma grande vantagem da migração é a limpeza da interface. Botões como "Editar" ou "Excluir" podem sair da
barra de ferramentas superior e ir direto para o Card. Para um ar de modernidade, troque os velhos
TSpeedButton por um TComboBox de ações.
E não precisa ser obrigatoriamente um ComboBox; você também pode utilizar um TPopupMenu vinculado a um ícone de "três pontinhos" ou até mesmo a um Label qualquer. Há muitos recursos visuais que você poderá usar para economizar espaço e/ou melhorar significativamente o aspecto visual do card, mantendo as opções de edição e exclusão acessíveis, mas discretas.
Não tenha medo do unicode
Diferente do Delphi, onde muitas vezes o editor da IDE vem configurado com o charset ANSI, o editor do Lazarus é puramente Unicode (UTF-8) por padrão. Isso significa que você pode usar caracteres especiais diretamente no seu código-fonte e nas propriedades dos componentes sem medo de "quebrar" o binário.
Nota para usuários Delphi: Diferente do Lazarus, a IDE do Delphi tradicionalmente utiliza ANSI como padrão no editor de código. Embora o programador possa alterar o encoding do arquivo para Unicode (UTF-8), em projetos legados essa mudança deve ser feita com cautela. Alterar o encoding de arquivos de código-fonte antigos pode causar problemas comuns, como a corrupção de caracteres especiais ou acentuados já existentes no arquivo, conflitos em ferramentas de controle de versão (Git/SVN) e possíveis erros de interpretação pelo compilador em versões mais antigas que não esperam arquivos gravados em UTF-8.
Veja como você pode usar labels para criar interfaces mais ricas sem precisar de arquivos de imagem adicionais:
procedure Tframe_RM_Item.lblMostrarMaisClick(Sender: TObject);
begin
if ContainsText(lblMostrarMais.Caption,' mais ') then
begin
MyNextPanel.Visible:=true;
lblMostrarMais.Caption:='Mostrar menos informações ▲';
end
else
begin
MyNextPanel.Visible:=false;
lblMostrarMais.Caption:='Mostrar mais informações ▼';
end;
end;
Percebeu os caracteres ▲, ▼, ◀ e▶?
Isso mesmo! Use-os e tantos outros que
conseguir. Evite poluir o seu projeto com arquivos de recursos (.res) ou imagens externas
quando sinais diacríticos, setas ou até mesmo emojis do Unicode estão disponíveis nativamente.
Para facilitar o seu trabalho, aqui estão alguns caracteres Unicode que funcionam muito bem em labels e botões para indicar ações de forma limpa e moderna:
| Símbolo | Ação Sugerida | Uso Típico |
|---|---|---|
| 🔍 ou ⌕ | Pesquisar | Botão de filtro ou busca rápida |
| 📝 ou ✎ | Editar | Abrir formulário de edição |
| 🗑️ ou 🗑 | Excluir | Remover registro (Cuidado: use com diálogo de confirmação) |
| ➕ ou ✚ | Novo | Adicionar novo Item/Registro |
| 💾 ou 💾 | Salvar | Gravar alterações no banco |
| ⚙️ ou ⚙ | Configurações | Opções do sistema ou do card |
| 🔄 ou ↷ | Atualizar | Recarregar dados (Refresh) |
| 📅 ou 📅 | Data | Selecionar período ou data de lançamento |
| 📂 ou 📁 | Abrir | Explorar arquivos ou pastas |
| ☰ ou ☰ | Menu | Menu hambúrguer para opções extras |
| 👍 e 👎 | Aprovar / Rejeitar | Feedback positivo ou negativo |
| ← ↑ → ↓ | Navegação | Setas direcionais para controle de fluxo |
| 🏠 ou 🏠 | Início | Voltar para a tela principal (Home) |
| 🔔 ou 🔔 | Avisos | Alertas e notificações do sistema |
| 🔒, 🔓 ou 🔒 | Privacidade | Indicar campos bloqueados/desbloqueados ou segurança |
| ⭐ ou ⭐ | Favorito | Marcar itens de destaque |
| 📋 ou 📋 | Copiar | Copiar conteúdo para a área de transferência |
| 📥 ou 📥 | Colar | Colar conteúdo da área de transferência |
| ▶︎, ⏸︎, ⏹︎ | Multimídia | Controles de Play, Pause e Stop |
| ▲, ▼, ◀, ▶ | Triângulos | Expansão, recolhimento e navegação lateral |
| ◄ e ► | Ponteiros Sólidos | Variações mais largas para botões de navegação lateral |
| ✓, ✅, ☐, ❌, ☑ | Seleção | Check, Uncheck e Selecionar Tudo (Check All) |
| ⏳, ⌛, ⏱️, ⏰ | Tempo | Processamentos, agendamentos e prazos |
| 💰, 💵, � | Financeiro | Moedas, pagamentos e valores monetários |
| ❗, ❓, ⚠️, 🚨, 🛑 | Alertas | Avisos, perguntas, perigo e interrupções |
| 💀, ☠️, 🚫 | Perigo/Proibido | Ações críticas, erros fatais ou restrições |
| 📧, ✉️, 💬, 📍 | Comunicação | E-mail, mensagens, balões de fala e localização |
| 👁️, 🙈 | Visualização | Exibir senha (olho aberto) ou ocultar (olho fechado) |
| ○ e ● | Geométricos | Círculos para estados de rádio ou indicadores cheios/vazios |
| ♻️ | Reciclagem | Processos de atualização ou reutilização |
| ©, ®, ™ | Jurídico | Copyright, Marca Registrada e Trade Mark |
| 🏁, 🚩, 🏳️, 🏴, 🟢, 🟡 | Estados (Status) | Bandeiras e cores para Início, Em andamento, Concluído e Alertas |
| 📥, ⬇️, ⏬ | Download | Baixar arquivos ou receber dados |
| 📤, ⬆️, ⏫ | Upload | Enviar arquivos ou carregar dados |
Para uma lista completa e visual, você pode consultar a lista de emojis e símbolos em: Emojitool.
Dica Pro: Usando códigos Unicode no Pascal
Se você preferir não "colar" o símbolo diretamente no seu código-fonte (para evitar problemas de
Encoding em editores de texto mal configurados), o Pascal permite que você use o código decimal
ou hexadecimal do caractere precedido pelo sinal de cerquilha (#).
Veja como ficaria o exemplo do "Mostrar mais" usando essa técnica:
// Usando códigos hexadecimais para os triângulos:
// #$25B2 = ▲ (Triângulo para cima)
// #$25BC = ▼ (Triângulo para baixo)
if ContainsText(lblMostrarMais.Caption, 'mais') then
begin
MyNextPanel.Visible := True;
lblMostrarMais.Caption := 'Mostrar menos informações ' + #$25B2;
end
else
begin
MyNextPanel.Visible := False;
lblMostrarMais.Caption := 'Mostrar mais informações ' + #$25BC;
end;
Essa abordagem é extremamente robusta e garante que o caractere correto será renderizado independentemente do sistema operacional ou da configuração de idioma do compilador.
Use efeitos de hyperlink nos Labels
Podemos usar eventos nos labels para criar um efeito de hyperlink similar aos navegadores, tornando a
interface mais intuitiva para ações. Por exemplo, no evento OnMouseEnter (quando o mouse
passa por cima do label), você pode torná-lo azul, sublinhado e negrito, além de trocar o ponteiro do
mouse para a "mãozinha" (Cursor := crHandPoint). Veja este código genérico que
serve para qualquer TLabel:
if (Sender is TLabel) then
begin
with (Sender as TLabel) do
begin
Font.Style := Font.Style + [fsBold, fsUnderline];
Font.Color := clBlue;
Cursor := crHandPoint;
end;
end;
E depois, use o evento OnMouseLeave (quando o mouse sai da área do label) para desfazer os
efeitos aplicados e retornar ao estado original:
if (Sender is TLabel) then
begin
with (Sender as TLabel) do
begin
Font.Style := Font.Style - [fsBold, fsUnderline];
Font.Color := clDefault;
Cursor := crDefault;
end;
end;
Veja como fica o efeito de hyperlink em labels:
Comparativo de Manutenibilidade
Para quem ainda tem dúvidas sobre qual caminho seguir, o quadro abaixo resume os ganhos reais de produtividade e organização ao adotar o uso de Frames em conjunto com o FlowPanel. Não se trata apenas de estética, mas de preparar seu projeto para o crescimento e para manutenções rápidas e seguras:
| Critério | Sem Frames (Gambiarra) | Com Frames + FlowPanel |
|---|---|---|
| Alterar Layout | Terror (mexer em cada item via código) | Fácil (muda apenas no Designer do Frame) |
| Depuração | Caótica (milhares de componentes soltos) | Organizada (lógica isolada no Frame) |
| Legibilidade | Baixa (Formulário principal gigante) | Alta (Código modular e limpo) |
| Reutilização | Nula (precisa copiar e colar componentes e código) | Alta (o mesmo card serve para várias telas do sistema) |
| Consistência | Frágil (mudar um padrão visual exige alteração manual em cada item) | Total (mudou no "molde", mudou em todo o projeto) |
| Trabalho em Equipe | Difícil (gera muitos conflitos na Unit principal) | Paralelo (um mexe no Card, outro na lógica da tela principal) |
| Testabilidade | Complexa (lógica misturada com a tela e o banco) | Simplificada (você testa o Frame isoladamente) |
Exemplo de Fluxo de Trabalho (O Jeito Certo)
1. A Estratégia de Dados: Desacoplamento é a Chave
Muitos programadores ainda caem na armadilha de usar o TQuery direto ou "espetar" os componentes do Frame diretamente ao DataSet da consulta principal. Funciona? Sim, mas é a típica técnica do "fazedor de telas" que não projeta para o futuro. Ao fazer essa ligação direta, você cria uma dependência rígida: o seu Card passa a ser um escravo daquela consulta específica, tornando impossível reutilizá-lo com dados vindos de outra origem.
Esse é o ponto crucial que separa os amadores dos arquitetos de software. Usar um
TMemTable ou carregar os dados para uma TObjectList é muito mais elegante.
Isso garante o desacoplamento: o seu Frame não precisa saber se os dados vêm de um
banco Firebird antigo, de um Postgres moderno ou de uma API JSON; ele apenas recebe os valores prontos e
os exibe.
Vale lembrar que componentes do tipo MemTable como TBufDataset, TZMemtable, TFDMemTable,
etc.,
possuem métodos poderosos para clonar outros datasets, seja apenas a estrutura ou a estrutura completa
com os dados
(como o CopyFromDataset). Não é necessário — e nem produtivo — ficar populando registro a
registro
via código manual para passar dados de um lado para o outro; afinal, isso seria como estar no jardim de
infância
na escola de programação. Além disso, ao usar memória, a atualização da tela torna-se instantânea, sem
estressar
a rede a cada refresh.
2. Implementando a Lógica Interna do Frame (usando getters e setters)
Para um código profissional, a abordagem mais limpa e orientada a objetos é usar Propriedades (Properties) com getters e setters diretamente no Frame para receber os dados que o Card precisa exibir. Isso encapsula a lógica e permite que o Frame reaja instantaneamente quando um dado é alterado.
{ TfrItemCard }
type
TfrItemCard = class(TFrame)
lblDescricao: TLabel;
lblEstoque: TLabel;
// ... outros componentes visuais ...
private
FCodigo: Integer;
FDescricao: string;
FValor: Currency;
FEstoque: Double;
procedure SetCodigo(const AValue: Integer);
procedure SetDescricao(const AValue: string);
procedure SetValor(const AValue: Currency);
procedure SetEstoque(const AValue: Double);
public
property Codigo: Integer read FCodigo write SetCodigo;
property Descricao: string read FDescricao write SetDescricao;
property Valor: Currency read FValor write SetValor;
property Estoque: Double read FEstoque write SetEstoque;
end;
implementation
// O método setter (SetCodigo) pode atualizar um Label ou Edit
procedure TfrItemCard.SetCodigo(const AValue: Integer);
begin
if FCodigo = AValue then Exit;
FCodigo := AValue;
// Exemplo: edtCodigo.Text := IntToStr(FCodigo);
// ou lblCodigo.Caption := IntToStr(FCodigo);
end;
// O método setter (SetDescricao) atualiza o visual assim que a propriedade recebe um valor
procedure TfrItemCard.SetDescricao(const AValue: string);
begin
if FDescricao = AValue then Exit;
FDescricao := AValue;
lblDescricao.Caption := FDescricao;
end;
// O método setter (SetValor) atualiza o visual formatando a moeda
procedure TfrItemCard.SetValor(const AValue: Currency);
begin
if FValor = AValue then Exit;
FValor := AValue;
// Supondo que você tenha um TLabel ou TEdit para o valor
// Exemplo: edtValor.Text := FormatFloat('"R$ ",0.00', FValor);
end;
// O método setter (SetEstoque) injeta a regra de negócio visual
procedure TfrItemCard.SetEstoque(const AValue: Double);
begin
if FEstoque = AValue then Exit;
FEstoque := AValue;
lblEstoque.Caption := 'Qtd: ' + FloatToStr(FEstoque);
// Lógica visual: Isolada e fácil de manter, executada automaticamente ao definir a propriedade!
if FEstoque <= 0 then
begin
lblEstoque.Font.Color := clRed;
lblEstoque.Font.Style := [fsBold];
end
else
begin
lblEstoque.Font.Color := clDefault;
lblEstoque.Font.Style := [];
end;
end;
3. O Loop de Criação (No Formulário Principal)
Com o Frame pronto e usando getters e setters, percorremos os dados e injetamo-los no card, aplicando o efeito Zebra para que cada card se identifique como diferente do outro. Neste exemplo, não usarei Bevels para criar o efeito de sombra ou gradientes para ser apenas didático, mas depois que dominar a técnica é muito provável que faça uso deles porque realmente deixa os cards muito bonitos. Por ora, quero que note a simplicidade em atribuir os valores diretamente às propriedades do Frame criado:
procedure TFormPrincipal.CarregarCards;
var
Card: TfrItemCard;
begin
FlowPanel1.DisableAlign; // Performance: Bloqueia realinhamento durante o loop
try
qPesquisa.Open;
// Limpa cards existentes
while FlowPanel1.ControlCount > 0 do
FlowPanel1.Controls[0].Free;
qPesquisa.First;
while not qPesquisa.EOF do
begin
Card := TfrItemCard.Create(Self);
Card.Parent := FlowPanel1;
Card.Name := 'Card_' + qPesquisa.FieldByName('ID').AsString;
// Vincula um menu de ações global ao botão do card
Card.lblMostrar_Opcoes.PopupMenu := Menu_Acoes;
// EFEITO ZEBRA: Alternância de cores
if Odd(FlowPanel1.ControlCount) then
Card.Color := $E9EFF5
else
Card.Color := $F7F9FB;
// Se o card tiver um botão do estilo "Aplicar" para efetivar modificações
// O botão no evento onclick deve ser assinalado para que o card saiba o que
// fazer apenas para aquele card
//Card.btnAplicar.OnClick := Card.btnAplicarClick;
// Injeção de dados direta via Propriedades
Card.Codigo := qPesquisa.FieldByName('ID').AsInteger;
Card.Descricao := qPesquisa.FieldByName('NOME').AsString;
Card.Estoque := qPesquisa.FieldByName('SALDO').AsFloat;
Card.Valor := qPesquisa.FieldByName('PRECO').AsCurrency;
qPesquisa.Next;
end;
finally
FlowPanel1.EnableAlign;
FlowPanel1.Realign;
end;
end;
Dica de Arquitetura: Note que vinculamos o PopupMenu (ou qualquer outro
evento de botão) que tenha implicações nos dados — como consultas ou alterações — diretamente no loop de
criação. Isto é fundamental: a Tela Principal (que conhece o contexto do banco de
dados) deve gerenciar as ações de negócio. Deixe para o Frame apenas os eventos de
interatividade puramente visual, como o revelamento progressivo, os efeitos de
hyperlink ou animações internas enquanto eventos de ações devem ser "linkados" para serem
executados na tela principal onde supostamente temos os componentes de acesso ao banco de dados.
Se você assinalou um evento de clique para um botão dentro do card (como o btnAplicar no
exemplo acima), você precisará saber de qual card veio o clique para obter os dados corretos. Você faz
isso
fazendo um cast (conversão de tipo) do parâmetro Sender ou acessando o
Parent do botão clicado:
procedure TFormPrincipal.btnAplicarClick(Sender: TObject);
var
CardClicado: TfrItemCard;
ComponenteAtual: TControl;
begin
if not (Sender is TControl) then Exit;
// O Parent do botão pode ser um Panel, um fluxo, ou o próprio Frame.
// Precisamos subir na hierarquia até encontrar o Frame (TfrItemCard).
ComponenteAtual := TControl(Sender);
CardClicado := nil;
while Assigned(ComponenteAtual) do
begin
if ComponenteAtual is TfrItemCard then
begin
CardClicado := TfrItemCard(ComponenteAtual);
Break;
end;
ComponenteAtual := ComponenteAtual.Parent;
end;
if Assigned(CardClicado) then
begin
// Agora você tem acesso total ao Frame que disparou o evento!
// Exemplo: pegando o texto de um Label interno (embora menos recomendado):
ShowMessage('Você clicou em aplicar no produto: ' + CardClicado.lblDescricao.Caption);
// O JEITO MAIS ELEGANTE: Acesse diretamente as propriedades que você criou no Frame!
// Ex: ProcessarVenda(CardClicado.Codigo, CardClicado.Valor);
end;
end;
Dicas de "Pulo do Gato"
- Scroll Automático: Quando o FlowPanel está cheio de Frames, o foco pode ficar
"preso" nos componentes internos e daí, você tenta rolar a tela com a rodinha do mouse e nada
acontece.
Para resolver, implemente o evento
OnMouseWheelno seu Frame e repasse o comando para o ScrollBox pai:procedure TfrItemCard.FrameMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); var P: TWinControl; SB: TScrollBox; begin // Repassa o scroll para o ScrollBox pai (evita foco preso nos cards) P := Parent; while Assigned(P) do begin if P is TScrollBox then begin SB := TScrollBox(P); SB.VertScrollBar.Position := SB.VertScrollBar.Position - WheelDelta; Handled := True; Exit; end; P := P.Parent; end; end;Um ponto de atenção: O exemplo acima considera que a rodinha do mouse vai rolar sobre um TFrame, mas se dentro do TFrame você colocar um painel (TPanel) e ingressar os componentes visuais dentro dele, então o código acima ao invés de usar o evento OnMouseWheel do TFrame, ele deve usar o evento OnMouseWheel do painel (TPanel).
- Constraints: No Frame, defina
Constraints.MinWidthpara impedir deformações visuais indesejadas. - Eventos de Clique: Lembre-se que o usuário clica nos Labels. Reatribua o
OnClickdos componentes internos para o evento no form principal que recebe o Frame. Como dito antes, se vocÊ colocar as ações dentro do frame, você irá perder a flexibilidade usar o mesmo frame em outros forms que poderiam exibi-lo sem as mesmas ações de negócio. - Estilo Visual: Use a propriedade
BevelEdges,BevelInner,BevelOuter,BevelKind,BevelWidth,BorderWidth,BorderStyle,Colore/ou umTShapeao fundo para criar efeito de profundidade.
Como fica nosso exemplo?
Ao aplicarmos todas as sugestões indicadas neste guia, nosso projeto se comportaria como mostrado no vídeo abaixo:
Note que neste exemplo, a alteração de cada card é individual. Para deixar isso claro, criei um botão "Aplicar" que apenas se habilita quando algo é alterado no card; ao clicar nele, a informação é gravada de forma indelével. Então, se você alterar dois ou mais cards simultaneamente, apenas os botões "Aplicar" destes cards estarão ligados ou disponíveis.
Caso queira o exemplo completo para testar em seu próprio computador, poderá baixá-lo do repositório: lazdemo_cards no GitHub.
O Resultado Final: Da Planilha Estática ao Design Fluido
Compare agora o impacto visual. Aquele TDBGrid denso e limitado que vimos no início deu
lugar a uma interface baseada em cartões. Note que, mesmo mantendo uma estética limpa e sem bibliotecas
externas de terceiros, o ganho em clareza é imediato.
As informações que antes lutavam por espaço em colunas rígidas agora respiram em um layout hierárquico, utilizando exibição progressiva para ocultar detalhes secundários e elementos visuais contextuais para destacar o que realmente importa. É a prova de que a robustez do Pascal pode — e deve — caminhar junto com um design moderno e eficiente:
Quando temos informações colunadas, isto é, um TEdit abaixo de cada TLabel e com repetição então é
possível comprimir ainda mais, basta criamos um cabeçalho usando TLabels no topo fora do
TFrame e
então colocar apenas as informações no TFrame sem os TLabels. Daí ficaria um card bem parecido ao DBGrid
inicial,
mas sem a complicação toda de programar os eventos OnDrawColumnCell, rolagens laterais, etc.
Conclusão
Abandonar o DBGrid não é apenas uma questão de estética, é usabilidade profissional. Usando Frames e FlowPanels, você pode até entregar a aparência de um DBGrid, mas sem a complexidade dele, além disso, entrega uma interface moderna sem sacrificar a robustez do Pascal. O seu "eu" daqui a 6 meses agradecerá por não ter feito uma gambiarra!