HotXLS levert TXLSWorkbookViewer, een native VCL-besturingselement dat XLS-, XLSX-, XLSM- en ODS-werkmappen rendert als een interactief spreadsheetgrid binnen een Delphi- of C++Builder-formulier, zonder Excel te installeren of via OLE-automatisering aan te sturen. Zo'n besturingselement goed bouwen betekent drie specifieke problemen oplossen: een muisklik die binnen een samengevoegde cel valt, correct koppelen aan de juiste logische cel, scrollpositie, headerbanden en celselectie consistent houden terwijl een gebruiker over een blad pant dat veel groter is dan het zichtbare venster, en beslissen wat een klik op een commentaarmarkering of een hyperlinkcel eigenlijk moet doen
De meeste Delphi-bedrijven grijpen naar een spreadsheetviewer om redenen die niets met bewerken te maken hebben: een auditstation dat geüploade werkmappen bekijkt voordat ze een pipeline binnenkomen, een kiosk- of rapportviewer waarbij Microsoft Office geen onderdeel is van de implementatie-image, of een QA-tool dat de inhoud van een werkmap moet tonen zonder de onvoorspelbaarheid van het automatiseren van een echt Excel-proces via COM. Een gewoon stringgrid levert snel tekst in cellen op, maar een spreadsheetbestand is geen gewoon grid: cellen worden samengevoegd tot blokken die in het onderliggende model maar één keer bestaan, bladen dragen vaste headerbanden en onafhankelijke horizontale en verticale scrollposities, en individuele cellen dragen commentaren en hyperlinks die hun eigen interactiemodel nodig hebben. TXLSWorkbookViewer is HotXLS's antwoord op dat gat, en het interne ontwerp ervan is een redelijke blauwdruk voor iedereen die vanaf nul een vergelijkbaar besturingselement bouwt
Hoe vermijdt een werkmapviewer afhankelijkheid van Excel?
TXLSWorkbookViewer vermijdt Excel volledig door te lezen via HotXLS's eigen geparste objectmodel in plaats van een document via Excel te openen en het via marionettenbesturing aan te sturen. De eigenschap Workbook bindt een bestaande TXLSWorkbook voor klassieke XLS-bestanden, en XlsxWorkbook bindt een TXLSXWorkbook voor XLSX-, XLSM- en sjabloonvarianten; elk van beide kan al elders in de toepassing geopend zijn, en de viewer leest er alleen van. Wanneer het besturingselement het bestand zelf moet bezitten, inspecteert LoadFromFile de extensie, leidt XLSX, XLSM, XLTX, XLTM en ODS via de moderne engine en al het andere via de klassieke, en geeft welke werkmap het ook heeft aangemaakt weer vrij zodra het besturingselement wordt gewist of vernietigd
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;
De juiste cel vinden binnen een samengevoegd bereik
Een klik omzetten naar de juiste cel in TXLSWorkbookViewer is een tweetraps opzoeking, en die scheiding is belangrijk omdat pixelgeometrie en spreadsheetsemantiek werkelijk verschillende problemen zijn. De eerste fase is pure geometrie: een private methode CellAtPoint loopt door kolombreedtes en rijhoogtes vanaf de huidige scrollpositie totdat het de band vindt die de aangeklikte X- en Y-coördinaat bevat, zonder enig besef van samengevoegde cellen. De tweede fase is semantisch: elk pad dat de selectie wijzigt, een muisklik, een pijltoets, Tab, of een directe aanroep van GoToCell, gaat via één interne ChangeSelection-routine, die de ruwe rij en kolom normaliseert tegen elke samenvoeging en deze vastklikt op de ankercel van de samenvoeging voordat de selectie daadwerkelijk verandert
Het anker is de linkerbovencel van het samengevoegde bereik, en het is de enige cel in dat blok die werkelijk een waarde, een opmaak, een commentaar of een hyperlink bevat in het onderliggende werkmapmodel; elke andere cel die de samenvoeging visueel bedekt, is leeg in de data zelf. Voor klassieke XLS-werkmappen komt het anker van Cell.MergeArea, een IXLSRange waarvan Row en Column naar de eigenaarscel wijzen; voor XLSX- en ODS-werkmappen geeft MergedCells.FindAt een TXLSXMergedRange terug die hetzelfde anker blootstelt als Row1 en Col1. Het tekenen lost een equivalent probleem onafhankelijk op, door de rechthoek van een samengevoegde cel uit te breiden naar de volledige rij- en kolomspanne en de cellen binnen die spanne over te slaan, zodat de selectieomlijning het hele samengevoegde blok omvat in plaats van alleen de ankerhoek, en samengevoegde lay-outs schrijven in plaats van ze alleen terug te lezen is een verwant maar apart probleem dat wordt behandeld in het begeleidende artikel over lay-out van samengevoegde cellen voor rapportsjablonen
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;
Wat houdt scrollen, headers en selectie synchroon?
TXLSWorkbookViewer houdt drie afzonderlijke stukjes toestand coherent: de logische scrollpositie vastgehouden in TopRow en LeftCol, de native Windows-scrollbalken die het besturingselement opvraagt via WS_HSCROLL en WS_VSCROLL in CreateParams, en de huidige selectie in SelectedRow en SelectedCol. Een scrollbalk slepen of aan het muiswiel draaien vuurt WM_HSCROLL, WM_VSCROLL, of WM_MOUSEWHEEL af, wat TopRow of LeftCol bijwerkt en opnieuw tekent; de selectie verplaatst niet mee, wat overeenkomt met hoe Excel zelf pannen scheidt van selecteren. Na elk van die updates duwt UpdateScrollBars de nieuwe positie terug in de native scrollbalk via SetScrollInfo, zodat de duim nooit uit de pas raakt met wat het grid daadwerkelijk toont
Toetsenbordnavigatie voert dezelfde synchronisatie in de tegenovergestelde richting uit: de selectie voorbij de rand van het zichtbare grid verplaatsen roept EnsureSelectionVisible aan, dat TopRow of LeftCol aanpast door werkelijke kolombreedtes en rijhoogtes op te tellen in plaats van simpelweg met één te verhogen, aangezien rijen en kolommen aangepaste groottes kunnen dragen, en roept vervolgens UpdateScrollBars aan zodat de duim weerspiegelt waar het toetsenbord de weergave net naartoe heeft gebracht. De rijnummer- en kolomletter-headerbanden, ingesteld via RowHeaderWidth en ColumnHeaderHeight, zijn het deel van dit besturingselement dat vast op het scherm blijft terwijl TopRow en LeftCol de data eronder scrollen, en dat is de mate waarin dit besturingselement zelf iets "bevriest": het is niet Excel's functie Deelvensters blokkeren, en er is geen ingebouwde manier om een willekeurige datarij of -kolom vast te zetten terwijl de rest van het blad eraan voorbij scrollt. Eén grens die de moeite waard is om te testen voordat u een viewer op bestanden loslaat die u niet volledig onder controle heeft, is dat TopRow en LeftCol niet worden begrensd tegen het werkelijke gebruikte bereik van het werkblad, dus een duim die naar zijn structurele limiet wordt gesleept kan terechtkomen op rij 1.048.576 of kolom 16.384 en een leeg grid tonen in plaats van de laatste rij of kolom die daadwerkelijk data bevat; werkmappen groot genoeg om dat merkbaar te maken, zijn meestal ook groot genoeg om de aandacht aan de laadkant nodig te hebben die wordt behandeld in het artikel over prestaties van grote werkmappen
Commentaren en hyperlinks koppelen aan muis- en selectiegebeurtenissen
TXLSWorkbookViewer behandelt commentaren en hyperlinks als attributen van welke cel ook momenteel is geselecteerd, in plaats van als hoverdoelen, dus SelectedCellCommentText, SelectedCellCommentAuthor, en SelectedCellHyperlink worden elke keer bijgewerkt dat OnSelectionChange afgaat, of de selectie nu is verplaatst door een muisklik, een pijltoets, of een aanroep van GoToCell. Een cel met commentaar krijgt een klein rood driehoekje getekend in de rechterbovenhoek als visuele aanwijzing, vergelijkbaar met Excel's eigen commentaarvlag, maar die markering is puur visueel; er zit geen door hover getriggerde tooltip ingebouwd in het besturingselement, dus een toepassing die een pop-up wil bij mouse-over in plaats van bij selectie, moet die laag zelf bouwen. Hyperlinkactivering werkt op dezelfde selectie-eerst-manier: dubbelklikken op een cel roept ActivateSelectedCell aan, dat SelectedCellHyperlink leest en, als deze niet leeg is, OnHyperlinkClick opwerpt met het doeladres en een var Handled: Boolean-parameter die de handler kan instellen
Wat OnHyperlinkClick niet doet, is net zo belangrijk: TXLSWorkbookViewer roept nooit zelf ShellExecute aan of opent zelf een browser, ongeacht of de handler Handled op true zet of op false laat. Navigatie, en elke beslissing over wat als een veilig doel telt, is volledig de verantwoordelijkheid van de hosttoepassing, wat de juiste standaard is voor een component dat geen idee heeft of het is ingebed in een vertrouwde interne tool of een viewer voor bestanden die een klant net heeft geüpload
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;
Selectiebereik en beperkingen van toetsenbordnavigatie
Selectie in TXLSWorkbookViewer is altijd een enkele logische cel, bijgehouden als SelectedRow en SelectedCol; er is geen rechthoekige selectie van meerdere cellen in het basisbesturingselement, dus elke functie die op een blok cellen moet werken, moet daarboven worden gebouwd in plaats van uit een selectie-object te worden gelezen. Toetsenbordondersteuning is bewust basaal: pijltoetsen verplaatsen één cel per keer, Home keert terug naar het begin van de rij of, met Ctrl, naar cel A1, Page Up en Page Down springen tien rijen, en Tab en Shift+Tab stappen door kolommen; er is geen Ctrl+pijl-sprong naar de rand van een datagebied en geen met Shift uitgebreide bereikselectie, dus gebruikers die rechtstreeks van Excel komen, zullen het gat opmerken op een dicht gevuld blad
Kolomlimieten worden afgedwongen op hetzelfde ChangeSelection-knelpunt dat de normalisatie van samenvoegingen afhandelt, en ze verschillen bewust per engine: een viewer gebonden aan een klassieke TXLSWorkbook begrenst bij kolom 256, het structurele plafond van het BIFF8-formaat, terwijl een viewer gebonden aan TXLSXWorkbook de moderne limiet van 16.384 kolommen respecteert die XLSX vanaf Excel 2007 heeft geërfd. Rijen worden in beide gevallen begrensd op 1.048.576, dus het praktische verschil tussen het openen van een legacy XLS-bestand en een XLSX-bestand in dezelfde viewer draait volledig om hoe ver naar rechts het grid u laat gaan
Niets hiervan is exotisch zodra het is opgesplitst in pixelopzoeking, ankernormalisatie, en een handvol berichtafhandelaars, maar de drie met elkaar in overeenstemming laten werken bij echte bestanden, met echte samenvoegingen, commentaren en hyperlinks, is het grootste deel van het werk in een component als deze. TXLSWorkbookViewer wordt geleverd als onderdeel van de standaard HotXLS Excel-component voor Delphi en C++Builder, naast de klassieke en XLSX-objectmodellen waaruit het rendert