Logo Gladiston Santana

Pascal: Delphi & Free Pascal

Desenvolvimento robusto, compilado e de alta performance.

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.

Exemplo de DBGrid clássico

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:

  1. Vá ao menu superior: File > New...
  2. Na árvore de opções, escolha Module e selecione Frame.
  3. O Lazarus abrirá uma área de design vazia. Salve-a como ufrItemCard.pas e dê o nome à classe de TfrItemCard.
  4. 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.

Lazarus 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:

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:

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"

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!