HotXLS dodáva TXLSWorkbookViewer, natívny ovládací prvok VCL, ktorý vykresľuje zošity XLS, XLSX, XLSM a ODS ako interaktívny tabuľkový grid vnútri formulára Delphi alebo C++Builder, bez inštalácie Excelu alebo jeho riadenia cez automatizáciu OLE. Postaviť takýto ovládací prvok dobre znamená vyriešiť tri konkrétne problémy: mapovať kliknutie myšou, ktoré skončí vnútri zlúčenej bunky, na správnu logickú bunku, udržiavať pozíciu posunu, pásma hlavičiek a výber bunky konzistentné, keď používateľ posúva hárok oveľa väčší než viditeľné okno, a rozhodnúť, čo by kliknutie na značku komentára alebo bunku s hypertextovým odkazom malo v skutočnosti urobiť
Väčšina firiem pracujúcich s Delphi siaha po prehliadači tabuliek z dôvodov, ktoré nemajú nič spoločné s úpravou: audítorská stanica, ktorá zobrazí náhľad nahraných zošitov ešte pred ich vstupom do pipeline, kiosk alebo prehliadač reportov, kde Microsoft Office nie je súčasťou nasadeného obrazu, alebo nástroj QA, ktorý potrebuje zobraziť obsah zošita bez nepredvídateľnosti automatizácie skutočného procesu Excelu cez COM. Obyčajný string grid vám rýchlo dá text v bunkách, no súbor tabuľky nie je obyčajný grid: bunky sa zlučujú do blokov, ktoré v podkladovom modeli existujú iba raz, hárky nesú pevné pásma hlavičiek a nezávislé horizontálne a vertikálne pozície posunu, a jednotlivé bunky nesú komentáre a hypertextové odkazy, ktoré potrebujú vlastný model interakcie. TXLSWorkbookViewer je odpoveďou HotXLS na túto medzeru, a jeho vnútorný návrh je rozumným plánom pre kohokoľvek, kto stavia podobný ovládací prvok od nuly
Ako sa prehliadač zošitov vyhýba závislosti na Exceli?
TXLSWorkbookViewer sa úplne vyhýba Excelu tak, že číta cez vlastný naparsovaný objektový model HotXLS namiesto toho, aby otváral dokument cez Excel a ovládal ho ako bábku. Vlastnosť Workbook naviaže existujúci TXLSWorkbook pre klasické súbory XLS, a XlsxWorkbook naviaže TXLSXWorkbook pre varianty XLSX, XLSM a šablóny; ktorýkoľvek z nich už môže byť otvorený inde v aplikácii, a prehliadač z neho iba číta. Keď má ovládací prvok vlastniť samotný súbor, LoadFromFile preskúma príponu, XLSX, XLSM, XLTX, XLTM a ODS presmeruje cez moderný engine a všetko ostatné cez klasický, a uvoľní ktorýkoľvek zošit, ktorý vytvoril, hneď ako sa ovládací prvok vyčistí alebo zničí
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;
Nájdenie správnej bunky vnútri zlúčeného rozsahu
Vyriešenie kliknutia na správnu bunku v TXLSWorkbookViewer je dvojstupňové vyhľadávanie, a toto rozdelenie je dôležité, pretože pixelová geometria a sémantika tabuľky sú naozaj odlišné problémy. Prvý stupeň je čistá geometria: súkromná metóda CellAtPoint prechádza šírky stĺpcov a výšky riadkov od aktuálnej pozície posunu, kým nenájde pásmo, ktoré obsahuje kliknuté súradnice X a Y, bez akéhokoľvek povedomia o zlúčených bunkách. Druhý stupeň je sémantický: každá cesta, ktorá mení výber, kliknutie myšou, kláves so šípkou, Tab, alebo priame volanie GoToCell, sa zlieva do jednej internej rutiny ChangeSelection, ktorá normalizuje surový riadok a stĺpec voči akémukoľvek zlúčeniu a pred skutočnou zmenou výberu ich prichytí ku kotviacej bunke zlúčenia
Kotva je ľavá horná bunka zlúčeného rozsahu, a je jedinou bunkou v tomto bloku, ktorá v podkladovom modeli zošita skutočne drží hodnotu, formát, komentár, alebo hypertextový odkaz; každá ďalšia bunka, ktorú zlúčenie vizuálne pokrýva, je v samotných dátach prázdna. Pre klasické zošity XLS kotva pochádza z Cell.MergeArea, IXLSRange, ktorého Row a Column ukazujú na vlastniacu bunku; pre zošity XLSX a ODS MergedCells.FindAt vráti TXLSXMergedRange sprístupňujúci tú istú kotvu ako Row1 a Col1. Kreslenie rieši ekvivalentný problém nezávisle, rozšíri obdĺžnik zlúčenej bunky na jej plný rozsah riadkov a stĺpcov a preskočí bunky vnútri tohto rozsahu, takže obrys výberu obalí celý zlúčený blok, nielen jeho kotviaci roh, a zapisovanie zlúčených rozložení namiesto ich iba spätného čítania je súvisiaci, no odlišný problém opísaný v sprievodnom článku o rozložení zlúčených buniek pre šablóny reportov
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 udržuje posúvanie, hlavičky a výber v synchronizácii?
TXLSWorkbookViewer udržuje tri samostatné kúsky stavu súdržné: logickú pozíciu posunu držanú v TopRow a LeftCol, natívne posuvníky Windows, o ktoré ovládací prvok žiada cez WS_HSCROLL a WS_VSCROLL v CreateParams, a aktuálny výber v SelectedRow a SelectedCol. Ťahanie posuvníka alebo krútenie kolieskom myši vyvolá WM_HSCROLL, WM_VSCROLL, alebo WM_MOUSEWHEEL, ktoré aktualizujú TopRow alebo LeftCol a prekreslia; výber sa nepohne, čo zodpovedá tomu, ako samotný Excel oddeľuje posúvanie od výberu. Po ktorejkoľvek z týchto aktualizácií UpdateScrollBars pretlačí novú pozíciu späť do natívneho posuvníka cez SetScrollInfo, takže jazdec sa nikdy neodchýli od toho, čo grid v skutočnosti zobrazuje
Navigácia klávesnicou beží tú istú synchronizáciu v opačnom smere: presun výberu za okraj viditeľného gridu zavolá EnsureSelectionVisible, ktorá posunie TopRow alebo LeftCol akumuláciou skutočných šírok stĺpcov a výšok riadkov namiesto jednoduchého inkrementovania o jednu, keďže riadky a stĺpce môžu niesť vlastné veľkosti, a potom zavolá UpdateScrollBars, aby jazdec odrážal, kamkoľvek klávesnica práve posunula pohľad. Pásma hlavičiek čísel riadkov a písmen stĺpcov, veľkostne určené cez RowHeaderWidth a ColumnHeaderHeight, sú tá časť tohto ovládacieho prvku, ktorá zostáva pevná na obrazovke, zatiaľ čo TopRow a LeftCol posúvajú dáta pod ňou, a to je rozsah, do akého tento ovládací prvok sám robí zmrazovanie: nie je to funkcia Zmraziť priečky Excelu, a neexistuje žiadny vstavaný spôsob, ako pripnúť ľubovoľný dátový riadok alebo stĺpec, zatiaľ čo zvyšok hárka pod ním posúva. Jedna hranica, ktorú sa oplatí otestovať ešte pred nasadením prehliadača nad súbormi, ktoré úplne nekontrolujete, je, že TopRow a LeftCol nie sú obmedzené voči skutočne použitému rozsahu hárka, takže jazdec ťahaný na svoj štrukturálny limit môže skončiť na riadku 1 048 576 alebo stĺpci 16 384 a zobraziť prázdny grid namiesto posledného riadka alebo stĺpca, ktorý v skutočnosti drží dáta; zošity dosť veľké na to, aby to bolo badateľné, sú zvyčajne aj dosť veľké na to, aby potrebovali pozornosť na strane načítania opísanú v článku o výkone veľkých zošitov
Zapojenie komentárov a hypertextových odkazov do udalostí myši a výberu
TXLSWorkbookViewer zaobchádza s komentármi a hypertextovými odkazmi ako s atribútmi aktuálne vybranej bunky, nie ako s cieľmi hoveru, takže SelectedCellCommentText, SelectedCellCommentAuthor, a SelectedCellHyperlink sa aktualizujú zakaždým, keď sa vyvolá OnSelectionChange, či už sa výber pohol kliknutím myšou, klávesom so šípkou, alebo volaním GoToCell. Bunka s komentárom dostane do svojho pravého horného rohu ako vizuálnu podnetku nakreslený malý červený trojuholník, podobne ako vlastný príznak komentára Excelu, no táto značka je čisto vizuálna; do ovládacieho prvku nie je vstavaný žiadny tooltip vyvolaný hoverom, takže aplikácia, ktorá chce popup pri prejdení myšou namiesto pri výbere, si musí túto vrstvu postaviť sama. Aktivácia hypertextového odkazu funguje rovnakým spôsobom najprv-cez-výber: dvojklik na bunku zavolá ActivateSelectedCell, ktorá prečíta SelectedCellHyperlink, a ak nie je prázdny, vyvolá OnHyperlinkClick s cieľovou adresou a parametrom var Handled: Boolean na nastavenie handlerom
Čo OnHyperlinkClick nerobí, je rovnako dôležité: TXLSWorkbookViewer nikdy sám nezavolá ShellExecute ani neotvorí prehliadač, bez ohľadu na to, či handler nastaví Handled na true, alebo ho nechá false. Navigácia, a akékoľvek rozhodnutie o tom, čo sa počíta ako bezpečný cieľ, je úplne zodpovednosťou hostiteľskej aplikácie, čo je správna predvolená hodnota pre komponent, ktorý nemá ani potuchy, či je vložený do dôveryhodného interného nástroja, alebo do prehliadača súborov, ktoré zákazník práve nahral
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;
Rozsah výberu a limity navigácie klávesnicou
Výber v TXLSWorkbookViewer je vždy jediná logická bunka, sledovaná ako SelectedRow a SelectedCol; v základnom ovládacom prvku neexistuje výber obdĺžnikového viacbunkového rozsahu, takže akákoľvek funkcia, ktorá potrebuje pracovať s blokom buniek, sa musí postaviť nad ním namiesto toho, aby sa čítala z objektu výberu. Pokrytie klávesnicou je zámerne základné: klávesy so šípkami posúvajú po jednej bunke naraz, Home sa vráti na začiatok riadka, alebo s Ctrl na bunku A1, Page Up a Page Down skočia o desať riadkov, a Tab a Shift+Tab kráčajú naprieč stĺpcami; neexistuje skok Ctrl+šípka na okraj dátovej oblasti, ani rozšírenie výberu rozsahu pomocou Shift, takže používatelia prichádzajúci priamo z Excelu si túto medzeru na hustom hárku všimnú
Limity stĺpcov sa presadzujú na tom istom hrdle ChangeSelection, ktoré rieši normalizáciu zlúčenia, a zámerne sa líšia podľa enginu: prehliadač naviazaný na klasický TXLSWorkbook orezáva na stĺpci 256, štrukturálny strop formátu BIFF8, zatiaľ čo ten naviazaný na TXLSXWorkbook rešpektuje moderný limit 16 384 stĺpcov, ktorý XLSX zdedil od Excelu 2007 ďalej. Riadky sú v oboch prípadoch obmedzené na 1 048 576, takže praktický rozdiel medzi otvorením staršieho súboru XLS a súboru XLSX v tom istom prehliadači je úplne o tom, ako ďaleko doprava vás grid ochotný pustí
Nič z toho nie je exotické, keď sa to rozloží na vyhľadávanie podľa pixelov, normalizáciu kotvy, a hŕstku obslužných rutín správ, no dosiahnuť, aby sa tieto tri zhodli nad skutočnými súbormi, so skutočnými zlúčeniami, komentármi a hypertextovými odkazmi, je väčšina práce v takomto komponente. TXLSWorkbookViewer sa dodáva ako súčasť štandardného komponentu HotXLS Excel pre Delphi a C++Builder, spolu s klasickým a XLSX objektovým modelom, z ktorého vykresľuje