Articol tehnic

Un grid de foaie de calcul personalizat în Delphi cu HotXLS

HotXLS livrează TXLSWorkbookViewer, un control VCL nativ care randează registre de lucru XLS, XLSX, XLSM și ODS ca un grid interactiv de foaie de calcul în interiorul unui formular Delphi sau C++Builder, fără a instala Excel sau a-l conduce prin automatizare OLE. Construirea bine a acestui tip de control înseamnă rezolvarea a trei probleme specifice: maparea unui clic de mouse care aterizează în interiorul unei celule îmbinate la celula logică corectă, păstrarea poziției de derulare, benzilor de antet și selecției de celule consistente pe măsură ce un utilizator panoramează o foaie mult mai mare decât fereastra vizibilă, și decizia asupra a ce ar trebui să facă efectiv un clic pe un marcator de comentariu sau o celulă hyperlink

Majoritatea firmelor Delphi apelează la un vizualizator de foi de calcul din motive care nu au nimic de-a face cu editarea: o stație de audit care previzualizează registre de lucru încărcate înainte de a intra într-o conductă, un chioșc sau un vizualizator de rapoarte unde Microsoft Office nu face parte din imaginea de implementare, sau un instrument QA care trebuie să arate conținutul unui registru de lucru fără imprevizibilitatea automatizării unui proces Excel real prin COM. Un simplu grid de șiruri vă oferă text în celule rapid, dar un fișier de foaie de calcul nu este un simplu grid: celulele se îmbină în blocuri care există doar o dată în modelul de bază, foile poartă benzi de antet fixe și poziții de derulare orizontală și verticală independente, iar celulele individuale poartă comentarii și hyperlink-uri care au nevoie de propriul model de interacțiune. TXLSWorkbookViewer este răspunsul HotXLS la acel gol, iar designul său intern este un plan rezonabil pentru oricine construiește un control similar de la zero

Cum evită un vizualizator de registru de lucru dependența de Excel?

TXLSWorkbookViewer evită Excel complet citind prin propriul model de obiecte analizat al HotXLS, în loc să deschidă un document prin Excel și să îl manipuleze ca o marionetă. Proprietatea Workbook leagă un TXLSWorkbook existent pentru fișiere XLS clasice, iar XlsxWorkbook leagă un TXLSXWorkbook pentru variante XLSX, XLSM și șablon; oricare poate fi deja deschis în altă parte a aplicației, iar vizualizatorul doar citește din el. Când controlul ar trebui să dețină fișierul însuși, LoadFromFile inspectează extensia, direcționează XLSX, XLSM, XLTX, XLTM și ODS prin motorul modern și orice altceva prin cel clasic, și eliberează orice registru de lucru a creat odată ce controlul este golit sau distrus

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;

Localizarea celulei corecte în interiorul unui interval îmbinat

Rezolvarea unui clic la celula corectă în TXLSWorkbookViewer este o căutare în două etape, iar separarea contează pentru că geometria de pixeli și semantica foii de calcul sunt probleme cu adevărat diferite. Prima etapă este geometrie pură: o metodă privată CellAtPoint parcurge lățimile de coloane și înălțimile de rânduri de la poziția curentă de derulare până când găsește banda care conține coordonatele X și Y ale clicului, fără nicio cunoștință despre celule îmbinate deloc. A doua etapă este semantică: fiecare cale care schimbă selecția, un clic de mouse, o tastă săgeată, Tab, sau un apel direct la GoToCell, se canalizează printr-o singură rutină internă ChangeSelection, care normalizează rândul și coloana brute față de orice îmbinare și le fixează la celula ancoră a îmbinării înainte ca selecția să se schimbe efectiv

Ancora este celula din colțul stânga-sus a intervalului îmbinat, și este singura celulă din acel bloc care efectiv conține o valoare, un format, un comentariu, sau un hyperlink în modelul de bază al registrului de lucru; fiecare altă celulă pe care îmbinarea o acoperă vizual este goală în datele propriu-zise. Pentru registrele de lucru XLS clasice, ancora vine din Cell.MergeArea, un IXLSRange ale cărui Row și Column indică spre celula proprietară; pentru registrele de lucru XLSX și ODS, MergedCells.FindAt returnează un TXLSXMergedRange care expune aceeași ancoră ca Row1 și Col1. Desenarea rezolvă o problemă echivalentă independent, extinzând dreptunghiul unei celule îmbinate la întinderea sa completă de rând și coloană și sărind peste celulele din interiorul acelei întinderi, astfel încât conturul de selecție învelește întregul bloc îmbinat, nu doar colțul său ancoră, iar scrierea de aspecte îmbinate, nu doar citirea lor înapoi, este o problemă înrudită dar distinctă, acoperită în articolul complementar despre aspectul celulelor îmbinate pentru șabloane de rapoarte

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;

Ce menține derularea, antetele și selecția în sincronizare?

TXLSWorkbookViewer menține trei bucăți separate de stare coerente: poziția logică de derulare ținută în TopRow și LeftCol, barele de derulare native Windows pe care controlul le cere prin WS_HSCROLL și WS_VSCROLL în CreateParams, și selecția curentă în SelectedRow și SelectedCol. Tragerea unei bare de derulare sau rotirea rotiței mouse-ului declanșează WM_HSCROLL, WM_VSCROLL, sau WM_MOUSEWHEEL, care actualizează TopRow sau LeftCol și redesenează; selecția nu se mișcă, ceea ce se potrivește cu modul în care Excel însuși separă panoramarea de selectare. După oricare din acele actualizări, UpdateScrollBars împinge noua poziție înapoi în bara de derulare nativă prin SetScrollInfo, astfel încât cursorul (thumb) nu se depărtează niciodată de acord cu ce arată efectiv gridul

Navigarea cu tastatura rulează aceeași sincronizare în direcția opusă: mutarea selecției dincolo de marginea gridului vizibil apelează EnsureSelectionVisible, care ajustează TopRow sau LeftCol acumulând lățimile reale de coloane și înălțimile reale de rânduri, în loc pur și simplu să incrementeze cu unu, întrucât rândurile și coloanele pot purta dimensiuni personalizate, apoi apelează UpdateScrollBars, astfel încât cursorul reflectă oriunde tocmai a dus tastatura vizualizarea. Benzile de antet cu numărul rândului și litera coloanei, dimensionate prin RowHeaderWidth și ColumnHeaderHeight, sunt partea acestui control care rămâne fixă pe ecran în timp ce TopRow și LeftCol derulează datele de dedesubt, iar aceasta este întinderea de înghețare pe care acest control o face de la sine: nu este funcționalitatea Freeze Panes din Excel, și nu există nicio modalitate încorporată de a fixa un rând sau o coloană arbitrară de date în timp ce restul foii derulează pe lângă ea. O limită care merită testată înainte de a livra un vizualizator peste fișiere pe care nu le controlați complet este că TopRow și LeftCol nu sunt limitate față de intervalul folosit real al foii de calcul, așa că un cursor tras la limita sa structurală poate ateriza pe rândul 1.048.576 sau coloana 16.384 și arăta un grid gol, în loc de ultimul rând sau coloana care chiar conține date; registrele de lucru suficient de mari încât să facă asta vizibil sunt de obicei și suficient de mari încât au nevoie de atenția pe partea de încărcare acoperită în articolul despre performanța registrelor de lucru mari

Conectarea comentariilor și hyperlink-urilor la evenimente de mouse și selecție

TXLSWorkbookViewer tratează comentariile și hyperlink-urile ca atribute ale oricărei celule este selectată în prezent, nu ca ținte de hover, așa că SelectedCellCommentText, SelectedCellCommentAuthor, și SelectedCellHyperlink se actualizează de fiecare dată când se declanșează OnSelectionChange, indiferent dacă selecția s-a mutat prin clic de mouse, tastă săgeată, sau un apel la GoToCell. Unei celule comentate i se pictează un mic triunghi roșu în colțul din dreapta-sus ca indiciu vizual, similar cu propriul indicator de comentariu al Excel, dar acel marcator este pur vizual; nu există niciun tooltip declanșat de hover încorporat în control, așa că o aplicație care dorește un popup la trecerea mouse-ului, nu la selecție, trebuie să construiască acel strat singură. Activarea hyperlink-urilor funcționează în același mod, cu selecția pe primul loc: dublul clic pe o celulă apelează ActivateSelectedCell, care citește SelectedCellHyperlink și, dacă nu este gol, ridică OnHyperlinkClick cu adresa țintă și un parametru var Handled: Boolean pe care handler-ul îl setează

Ce nu face OnHyperlinkClick este la fel de important: TXLSWorkbookViewer nu apelează niciodată ShellExecute și nici nu deschide un browser de la sine, indiferent dacă handler-ul setează Handled la true sau îl lasă false. Navigarea, și orice decizie despre ce contează ca țintă sigură, este în întregime responsabilitatea aplicației gazdă, ceea ce este implicitul corect pentru o componentă care nu are nicio idee dacă este încorporată într-un instrument intern de încredere sau un vizualizator pentru fișiere pe care un client tocmai le-a încărcat

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;

Domeniul selecției și limitele navigării cu tastatura

Selecția în TXLSWorkbookViewer este întotdeauna o singură celulă logică, urmărită ca SelectedRow și SelectedCol; nu există selecție de interval rectangular multi-celulă în controlul de bază, așa că orice funcționalitate care trebuie să acționeze pe un bloc de celule trebuie construită deasupra lui, nu citită dintr-un obiect de selecție. Acoperirea tastaturii este deliberat de bază: tastele săgeți mută o celulă odată, Home revine la începutul rândului sau, cu Ctrl, la celula A1, Page Up și Page Down sar zece rânduri, iar Tab și Shift+Tab pășesc peste coloane; nu există niciun salt Ctrl+Săgeată la marginea unei regiuni de date și nicio selecție de interval extinsă cu Shift, așa că utilizatorii veniți direct din Excel vor observa golul pe o foaie densă

Limitele de coloane sunt impuse la același punct de strangulare ChangeSelection care gestionează normalizarea îmbinării, și diferă pe motor cu intenție: un vizualizator legat de un TXLSWorkbook clasic limitează la coloana 256, tavanul structural al formatului BIFF8, în timp ce unul legat de TXLSXWorkbook respectă limita modernă de 16.384 de coloane pe care XLSX a moștenit-o de la Excel 2007 înainte. Rândurile sunt plafonate la 1.048.576 în ambele cazuri, așa că diferența practică dintre deschiderea unui fișier XLS moștenit și a unui fișier XLSX în același vizualizator este în întregime despre cât de departe la dreapta este dispus gridul să vă lase să mergeți

Nimic din toate acestea nu este exotic odată descompus în căutare de pixeli, normalizare de ancoră, și o mână de handlere de mesaje, dar a face pe cele trei să fie de acord sub fișiere reale, cu îmbinări reale, comentarii și hyperlink-uri, este majoritatea muncii într-o componentă ca aceasta. TXLSWorkbookViewer este livrat ca parte a componentei Excel HotXLS standard pentru Delphi și C++Builder, alături de modelele de obiecte clasic și XLSX din care randează