O HotXLS distribui TXLSWorkbookViewer, um controlo VCL nativo que renderiza livros de cálculo XLS, XLSX, XLSM, e ODS como uma grelha de folha de cálculo interativa dentro de um formulário Delphi ou C++Builder, sem instalar o Excel nem o conduzir através de automação OLE. Construir bem esse tipo de controlo significa resolver três problemas específicos: mapear um clique de rato que caia dentro de uma célula combinada para a célula lógica correta, manter a posição de scroll, as faixas de cabeçalho, e a seleção de célula consistentes à medida que um utilizador percorre uma folha muito maior do que a janela visível, e decidir o que um clique num marcador de comentário ou numa célula de hiperligação deve efetivamente fazer
A maioria das empresas Delphi recorre a um visualizador de folhas de cálculo por razões que nada têm a ver com edição: uma estação de auditoria que pré-visualiza livros de cálculo carregados antes de entrarem num pipeline, um quiosque ou visualizador de relatórios onde o Microsoft Office não faz parte da imagem de implantação, ou uma ferramenta de QA que precisa de mostrar o conteúdo de um livro de cálculo sem a imprevisibilidade de automatizar um verdadeiro processo do Excel via COM. Uma simples grelha de cadeias dá texto em células rapidamente, mas um ficheiro de folha de cálculo não é uma grelha simples: as células combinam-se em blocos que só existem uma vez no modelo subjacente, as folhas transportam faixas de cabeçalho fixas e posições de scroll horizontal e vertical independentes, e células individuais transportam comentários e hiperligações que precisam do seu próprio modelo de interação. TXLSWorkbookViewer é a resposta do HotXLS a essa lacuna, e o seu design interno é um plano razoável para quem construir um controlo semelhante de raiz
Como evita um visualizador de livros de cálculo depender do Excel?
O TXLSWorkbookViewer evita inteiramente o Excel lendo através do próprio modelo de objetos analisado do HotXLS, em vez de abrir um documento através do Excel e o marionetizar. A propriedade Workbook vincula um TXLSWorkbook existente para ficheiros XLS clássicos, e XlsxWorkbook vincula um TXLSXWorkbook para variantes XLSX, XLSM, e de modelo; qualquer um dos dois já pode estar aberto noutro ponto da aplicação, e o visualizador limita-se a ler dele. Quando o controlo deve possuir o próprio ficheiro, LoadFromFile inspeciona a extensão, encaminha XLSX, XLSM, XLTX, XLTM, e ODS através do motor moderno e tudo o resto através do clássico, e liberta o livro de cálculo que criou assim que o controlo é 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;
Localizar a célula correta dentro de um intervalo combinado
Resolver um clique para a célula correta no TXLSWorkbookViewer é uma pesquisa em duas fases, e a divisão importa porque a geometria de pixel e a semântica de folha de cálculo são genuinamente problemas diferentes. A primeira fase é geometria pura: um método privado CellAtPoint percorre larguras de coluna e alturas de linha a partir da posição de scroll atual até encontrar a faixa que contém a coordenada X e Y clicada, sem qualquer consciência de células combinadas. A segunda fase é semântica: cada caminho que altera a seleção, um clique de rato, uma tecla de seta, Tab, ou uma chamada direta a GoToCell, converge para uma rotina interna ChangeSelection, que normaliza a linha e coluna em bruto em função de qualquer combinação e as encaixa na célula-âncora da combinação antes de a seleção efetivamente mudar
A âncora é a célula superior esquerda do intervalo combinado, e é a única célula desse bloco que genuinamente detém um valor, um formato, um comentário, ou uma hiperligação no modelo de livro de cálculo subjacente; cada outra célula que a combinação cobre visualmente está vazia nos próprios dados. Para livros de cálculo XLS clássicos, a âncora vem de Cell.MergeArea, um IXLSRange cujos Row e Column apontam para a célula proprietária; para livros de cálculo XLSX e ODS, MergedCells.FindAt devolve um TXLSXMergedRange expondo a mesma âncora como Row1 e Col1. A pintura resolve um problema equivalente de forma independente, expandindo o retângulo de uma célula combinada até ao seu vão completo de linha e coluna e saltando as células dentro desse vão, para que o contorno de seleção envolva todo o bloco combinado e não apenas o seu canto de âncora, e escrever layouts combinados em vez de apenas os ler de volta é um problema relacionado mas distinto, abordado no artigo complementar sobre layout de células combinadas 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 o scroll, os cabeçalhos, e a seleção sincronizados?
O TXLSWorkbookViewer mantém coerentes três peças de estado separadas: a posição de scroll lógica mantida em TopRow e LeftCol, as barras de scroll nativas do Windows que o controlo pede através de WS_HSCROLL e WS_VSCROLL em CreateParams, e a seleção atual em SelectedRow e SelectedCol. Arrastar uma barra de scroll ou rodar a roda do rato 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 à forma como o próprio Excel separa o deslocamento da seleção. Depois de qualquer uma dessas atualizações, UpdateScrollBars empurra a nova posição de volta para a barra de scroll nativa através de SetScrollInfo, pelo que o indicador nunca fica em desacordo com o que a grelha efetivamente mostra
A navegação por teclado executa a mesma sincronização na direção oposta: mover a seleção para além da borda da grelha visível chama EnsureSelectionVisible, que empurra TopRow ou LeftCol acumulando larguras de coluna e alturas de linha reais em vez de simplesmente incrementar um a um, uma vez que linhas e colunas podem transportar tamanhos personalizados, e depois chama UpdateScrollBars para que o indicador reflita para onde o teclado acabou de levar a vista. As faixas de cabeçalho de número de linha e letra de coluna, dimensionadas através de RowHeaderWidth e ColumnHeaderHeight, são a parte deste controlo que permanece fixa no ecrã enquanto TopRow e LeftCol fazem os dados por baixo deslizarem, e é essa a extensão de fixação que este controlo faz por conta própria: não é a funcionalidade Congelar Painéis do Excel, e não há forma incorporada de fixar uma linha ou coluna de dados arbitrária enquanto o resto da folha desliza. Um limite que vale a pena testar antes de distribuir um visualizador sobre ficheiros que não controla inteiramente é que TopRow e LeftCol não estão limitados ao intervalo efetivamente usado da folha de cálculo, pelo que um indicador arrastado até ao seu limite estrutural pode cair na linha 1.048.576 ou na coluna 16.384 e mostrar uma grelha em branco em vez da última linha ou coluna que efetivamente contém dados; livros de cálculo suficientemente grandes para tornar isso percetível são normalmente também suficientemente grandes para precisar da atenção do lado do carregamento abordada no artigo sobre desempenho de grandes livros de cálculo
Ligar comentários e hiperligações a eventos de rato e seleção
O TXLSWorkbookViewer trata comentários e hiperligações como atributos de qualquer célula atualmente selecionada, em vez de como alvos de hover, pelo que SelectedCellCommentText, SelectedCellCommentAuthor, e SelectedCellHyperlink atualizam sempre que OnSelectionChange dispara, quer a seleção se tenha movido por clique de rato, tecla de seta, ou uma chamada a GoToCell. Uma célula comentada recebe um pequeno triângulo vermelho pintado no seu canto superior direito como pista visual, semelhante à própria bandeira de comentário do Excel, mas esse marcador é puramente visual; não há qualquer dica de ferramenta acionada por hover incorporada no controlo, pelo que uma aplicação que queira um popup ao passar o rato em vez de na seleção tem de construir essa camada por conta própria. A ativação de hiperligações funciona da mesma forma orientada à seleção: duplo clique numa célula chama ActivateSelectedCell, que lê SelectedCellHyperlink e, se não estiver vazia, levanta OnHyperlinkClick com o endereço alvo e um parâmetro var Handled: Boolean para o manipulador definir
O que OnHyperlinkClick não faz é tão importante quanto o resto: o TXLSWorkbookViewer nunca chama ShellExecute nem abre um navegador por conta própria, independentemente de o manipulador definir Handled como verdadeiro ou o deixar como falso. A navegação, e qualquer decisão sobre o que conta como um alvo seguro, é inteiramente responsabilidade da aplicação hospedeira, o que é a predefinição certa para um componente que não faz ideia se está incorporado numa ferramenta interna de confiança ou num visualizador para ficheiros que um cliente acabou de carregar
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;
Âmbito de seleção e limites de navegação por teclado
A seleção no TXLSWorkbookViewer é sempre uma única célula lógica, rastreada como SelectedRow e SelectedCol; não há seleção de intervalo retangular multi-célula no controlo base, pelo que qualquer funcionalidade que precise de agir sobre um bloco de células tem de ser construída por cima dele, em vez de lida a partir de um objeto de seleção. A cobertura de teclado é deliberadamente básica: as teclas de seta movem uma célula de cada vez, Home regressa ao início da linha ou, com Ctrl, à célula A1, Page Up e Page Down saltam dez linhas, e Tab e Shift+Tab percorrem colunas; não há salto Ctrl+Seta até à borda de uma região de dados nem seleção de intervalo estendida por Shift, pelo que utilizadores vindos diretamente do Excel notarão a lacuna numa folha densa
Os limites de coluna são aplicados no mesmo ponto de estrangulamento ChangeSelection que trata a normalização de combinação, e diferem por motor de propósito: um visualizador vinculado a um TXLSWorkbook clássico limita-se à coluna 256, 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. As linhas estão limitadas a 1.048.576 em ambos os casos, pelo que a diferença prática entre abrir um ficheiro XLS legado e um ficheiro XLSX no mesmo visualizador está inteiramente em até onde para a direita a grelha está disposta a deixar ir
Nada disto é exótico assim que se decompõe em pesquisa de pixel, normalização de âncora, e um punhado de manipuladores de mensagem, mas fazer os três concordarem sob ficheiros reais, com combinações, comentários, e hiperligações reais, é a maior parte do trabalho num componente como este. TXLSWorkbookViewer é distribuído como parte do componente Excel HotXLS standard para Delphi e C++Builder, a par dos modelos de objeto clássico e XLSX a partir dos quais renderiza