Artigo Técnico

Um Grid de Planilha Personalizado em Delphi com o HotXLS

O HotXLS traz TXLSWorkbookViewer, um controle VCL nativo que renderiza pastas de trabalho XLS, XLSX, XLSM e ODS como um grid de planilha interativo dentro de um formulário Delphi ou C++Builder, sem instalar o Excel ou conduzi-lo por automação OLE. Construir esse tipo de controle bem significa resolver três problemas específicos: mapear um clique de mouse que pousa dentro de uma célula mesclada para a célula lógica correta, manter posição de rolagem, faixas de cabeçalho e seleção de célula consistentes enquanto um usuário percorre uma planilha muito maior que a janela visível, e decidir o que um clique em um marcador de comentário ou em uma célula de hyperlink deve de fato fazer

A maioria das empresas Delphi recorre a um visualizador de planilha por motivos que não têm nada a ver com edição: uma estação de auditoria que pré-visualiza pastas de trabalho enviadas antes de entrarem em um pipeline, um quiosque ou visualizador de relatório onde o Microsoft Office não faz parte da imagem de implantação, ou uma ferramenta de QA que precisa mostrar o conteúdo de uma pasta de trabalho sem a imprevisibilidade de automatizar um processo Excel real via COM. Um grid de string simples entrega texto em células rapidamente, mas um arquivo de planilha não é um grid simples: células se mesclam em blocos que só existem uma vez no modelo subjacente, planilhas carregam faixas de cabeçalho fixas e posições de rolagem horizontal e vertical independentes, e células individuais carregam comentários e hyperlinks que precisam de seu próprio modelo de interação. TXLSWorkbookViewer é a resposta do HotXLS para essa lacuna, e seu design interno é um esboço razoável para qualquer um construindo um controle similar do zero

Como um visualizador de pasta de trabalho evita depender do Excel?

TXLSWorkbookViewer evita o Excel por completo lendo através do próprio modelo de objeto analisado do HotXLS, em vez de abrir um documento pelo Excel e manipulá-lo como marionete. A propriedade Workbook vincula um TXLSWorkbook existente para arquivos XLS clássicos, e XlsxWorkbook vincula um TXLSXWorkbook para variantes XLSX, XLSM e template; qualquer um dos dois já pode estar aberto em outro lugar na aplicação, e o visualizador só lê a partir dele. Quando o controle deve possuir o próprio arquivo, LoadFromFile inspeciona a extensão, roteia XLSX, XLSM, XLTX, XLTM e ODS pelo motor moderno e tudo mais pelo clássico, e libera qualquer pasta de trabalho que criou assim que o controle é limpo ou destruído

var
  Viewer: TXLSWorkbookViewer;
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  if Book.Open('quarterly-report.xlsx') <> 1 then
    raise Exception.Create('Could not open workbook');

  Viewer := TXLSWorkbookViewer.Create(Self);
  Viewer.Parent := Self;
  Viewer.Align := alClient;
  Viewer.XlsxWorkbook := Book;        // the viewer does not take ownership
  Viewer.GoToCell(1, 1);

  Caption := Viewer.WorksheetName + ': ' + Viewer.SelectedCellText;
end;

Localizando a célula certa dentro de um intervalo mesclado

Resolver um clique para a célula correta em TXLSWorkbookViewer é uma busca em dois estágios, e a divisão importa porque geometria de pixel e semântica de planilha são problemas genuinamente diferentes. O primeiro estágio é geometria pura: um método privado CellAtPoint percorre larguras de coluna e alturas de linha a partir da posição de rolagem atual até encontrar a faixa que contém a coordenada X e Y clicada, sem nenhuma consciência de células mescladas. O segundo estágio é semântico: todo caminho que muda a seleção — um clique de mouse, uma tecla de seta, Tab, ou uma chamada direta a GoToCell — canaliza por uma única rotina interna ChangeSelection, que normaliza a linha e coluna brutas contra qualquer mesclagem e as encaixa na célula âncora da mesclagem antes de a seleção de fato mudar

A âncora é a célula superior esquerda do intervalo mesclado, e é a única célula naquele bloco que genuinamente contém um valor, um formato, um comentário ou um hyperlink no modelo de pasta de trabalho subjacente; toda outra célula que a mesclagem cobre visualmente está vazia nos próprios dados. Para pastas de trabalho XLS clássicas, a âncora vem de Cell.MergeArea, um IXLSRange cujo Row e Column apontam para a célula proprietária; para pastas de trabalho XLSX e ODS, MergedCells.FindAt retorna um TXLSXMergedRange expondo a mesma âncora como Row1 e Col1. A pintura resolve um problema equivalente independentemente, expandindo o retângulo de uma célula mesclada para todo seu alcance de linha e coluna e pulando as células dentro desse alcance, de modo que o contorno de seleção envolve todo o bloco mesclado, não apenas seu canto âncora, e escrever layouts mesclados em vez de apenas lê-los de volta é um problema relacionado, mas distinto, coberto no artigo complementar sobre layout de células mescladas para modelos de relatório

var
  Sheet: TXLSXWorksheet;
begin
  Sheet := Book.Sheets.Add('Summary');
  Sheet.MergeCells(2, 2, 3, 4);       // B2:D3
  Sheet.Cells[2, 2].Value := 'Region totals';

  Viewer.XlsxWorkbook := Book;
  Viewer.GoToCell(3, 4);              // targets the bottom-right corner of the merge
  // SelectedRow is now 2 and SelectedCol is now 2: normalized to the anchor cell
end;

O que mantém rolagem, cabeçalhos e seleção sincronizados?

TXLSWorkbookViewer mantém três pedaços de estado separados coerentes: a posição de rolagem lógica mantida em TopRow e LeftCol, as barras de rolagem nativas do Windows que o controle solicita por meio de WS_HSCROLL e WS_VSCROLL em CreateParams, e a seleção atual em SelectedRow e SelectedCol. Arrastar uma barra de rolagem ou girar a roda do mouse dispara WM_HSCROLL, WM_VSCROLL ou WM_MOUSEWHEEL, que atualizam TopRow ou LeftCol e repintam; a seleção não se move, o que corresponde a como o próprio Excel separa navegação de seleção. Depois de qualquer uma dessas atualizações, UpdateScrollBars empurra a nova posição de volta para a barra de rolagem nativa por meio de SetScrollInfo, de modo que o thumb (o marcador arrastável) nunca fica em desacordo com o que o grid está de fato mostrando

A navegação por teclado roda a mesma sincronização na direção oposta: mover a seleção além da borda do grid visível chama EnsureSelectionVisible, que empurra TopRow ou LeftCol acumulando larguras de coluna e alturas de linha reais em vez de simplesmente incrementar em um, já que linhas e colunas podem carregar tamanhos personalizados, e depois chama UpdateScrollBars de modo que o thumb reflita para onde o teclado acabou de levar a visão. As faixas de cabeçalho de número de linha e letra de coluna, dimensionadas por meio de RowHeaderWidth e ColumnHeaderHeight, são a parte deste controle que permanece fixa na tela enquanto TopRow e LeftCol rolam os dados por baixo, e essa é a extensão de congelamento que este controle faz por conta própria: não é o recurso Congelar Painéis do Excel, e não há forma embutida de fixar uma linha ou coluna de dados arbitrária enquanto o resto da planilha rola por baixo dela. Um limite que vale a pena testar antes de lançar um visualizador sobre arquivos que você não controla totalmente é que TopRow e LeftCol não são fixados contra o intervalo realmente usado da planilha, de modo que um thumb arrastado até seu limite estrutural pode pousar na linha 1.048.576 ou coluna 16.384 e mostrar um grid em branco, em vez da última linha ou coluna que de fato contém dados; pastas de trabalho grandes o bastante para tornar isso perceptível geralmente também são grandes o bastante para precisar da atenção do lado de carregamento coberta no artigo de desempenho de pastas de trabalho grandes

Conectando comentários e hyperlinks a eventos de mouse e seleção

TXLSWorkbookViewer trata comentários e hyperlinks como atributos de qualquer célula que esteja atualmente selecionada, em vez de alvos de hover, de modo que SelectedCellCommentText, SelectedCellCommentAuthor e SelectedCellHyperlink se atualizam toda vez que OnSelectionChange dispara, seja a seleção movida por clique de mouse, tecla de seta, ou uma chamada a GoToCell. Uma célula comentada recebe um pequeno triângulo vermelho pintado em seu canto superior direito como sinal visual, similar à própria flag de comentário do Excel, mas esse marcador é puramente visual; não há tooltip disparada por hover embutida no controle, de modo que uma aplicação que queira um popup ao passar o mouse, em vez de na seleção, precisa construir essa camada por conta própria. A ativação de hyperlink funciona da mesma forma orientada por seleção: dar duplo clique em uma célula chama ActivateSelectedCell, que lê SelectedCellHyperlink e, se não estiver vazio, levanta OnHyperlinkClick com o endereço alvo e um parâmetro var Handled: Boolean para o handler definir

O que OnHyperlinkClick não faz é tão importante quanto: TXLSWorkbookViewer nunca chama ShellExecute ou abre um navegador por conta própria, independentemente de o handler definir Handled como verdadeiro ou deixá-lo falso. A navegação, e qualquer decisão sobre o que conta como um alvo seguro, é inteiramente responsabilidade da aplicação hospedeira, que é o padrão certo para um componente que não faz ideia se está embutido em uma ferramenta interna confiável ou em um visualizador para arquivos que um cliente acabou de enviar

procedure TMainForm.ViewerSelectionChange(Sender: TObject; Row, Col: Integer);
begin
  if Viewer.SelectedCellCommentText <> '' then
    StatusBar.SimpleText := Viewer.SelectedCellCommentAuthor + ': ' +
      Viewer.SelectedCellCommentText
  else
    StatusBar.SimpleText := Viewer.SelectedCellHyperlink;
end;

procedure TMainForm.ViewerHyperlinkClick(Sender: TObject;
  const Target: WideString; var Handled: Boolean);
begin
  ShellExecute(0, 'open', PWideChar(Target), nil, nil, SW_SHOWNORMAL);
  Handled := True;
end;

Escopo de seleção e limites de navegação por teclado

A seleção em TXLSWorkbookViewer é sempre uma única célula lógica, rastreada como SelectedRow e SelectedCol; não há seleção retangular de múltiplas células no controle base, de modo que qualquer recurso que precise agir sobre um bloco de células precisa ser construído acima dele, em vez de lido de um objeto de seleção. A cobertura de teclado é deliberadamente básica: teclas de seta movem uma célula por vez, Home retorna ao início da linha ou, com Ctrl, para a célula A1, Page Up e Page Down pulam dez linhas, e Tab e Shift+Tab avançam pelas colunas; não há salto Ctrl+Seta para a borda de uma região de dados e nenhuma seleção de intervalo estendida por Shift, de modo que usuários vindos direto do Excel vão notar a lacuna em uma planilha densa

Limites de coluna são impostos no mesmo ponto de estrangulamento ChangeSelection que trata a normalização de mesclagem, e diferem por motor de propósito: um visualizador vinculado a um TXLSWorkbook clássico fixa em 256 colunas, o teto estrutural do formato BIFF8, enquanto um vinculado a TXLSXWorkbook respeita o limite moderno de 16.384 colunas que o XLSX herdou do Excel 2007 em diante. Linhas são limitadas a 1.048.576 de qualquer forma, de modo que a diferença prática entre abrir um arquivo XLS legado e um arquivo XLSX no mesmo visualizador é inteiramente sobre até onde à direita o grid está disposto a deixar você ir

Nada disso é exótico uma vez decomposto em busca de pixel, normalização de âncora e um punhado de manipuladores de mensagem, mas fazer os três concordarem sob arquivos reais, com mesclagens, comentários e hyperlinks reais, é a maior parte do trabalho em um componente assim. TXLSWorkbookViewer vem como parte do Componente Excel HotXLS padrão para Delphi e C++Builder, ao lado dos modelos de objeto clássico e XLSX a partir dos quais renderiza