Teknisk artikkel

Et egendefinert regnearkrutenett i Delphi med HotXLS

HotXLS leverer TXLSWorkbookViewer, en native VCL-kontroll som gjengir XLS-, XLSX-, XLSM-, og ODS-arbeidsbøker som et interaktivt regnearkrutenett inne i et Delphi- eller C++Builder-skjema, uten å installere Excel eller drive det gjennom OLE-automatisering. Å bygge den typen kontroll godt betyr å løse tre spesifikke problemer: å kartlegge et museklikk som havner inne i en sammenslått celle til den korrekte logiske cellen, å holde rulleposisjon, hodebånd, og cellevalg konsistente etter hvert som en bruker panorerer et ark langt større enn det synlige vinduet, og å avgjøre hva et klikk på en kommentarmarkør eller en hyperlenke-celle faktisk skal gjøre

De fleste Delphi-firmaer griper til en regnearkfremviser av grunner som ikke har noe med redigering å gjøre: en revisjonsstasjon som forhåndsviser opplastede arbeidsbøker før de går inn i en pipeline, en kiosk- eller rapportfremviser der Microsoft Office ikke er en del av utrullingsbildet, eller et QA-verktøy som trenger å vise en arbeidsboks innhold uten uforutsigbarheten ved å automatisere en ekte Excel-prosess over COM. Et rent stringgrid gir deg raskt tekst i celler, men en regnearkfil er ikke et rent rutenett: celler slår seg sammen til blokker som bare eksisterer én gang i den underliggende modellen, ark bærer faste hodebånd og uavhengige horisontale og vertikale rulleposisjoner, og individuelle celler bærer kommentarer og hyperlenker som trenger sin egen interaksjonsmodell. TXLSWorkbookViewer er HotXLS' svar på det gapet, og det interne designet er en fornuftig mal for alle som bygger en lignende kontroll fra bunnen av

Hvordan unngår en arbeidsbok-fremviser å avhenge av Excel?

TXLSWorkbookViewer unngår Excel fullstendig ved å lese gjennom HotXLS' egen parsede objektmodell i stedet for å åpne et dokument gjennom Excel og fjernstyre det som en marionett. Workbook-egenskapen binder en eksisterende TXLSWorkbook for klassiske XLS-filer, og XlsxWorkbook binder en TXLSXWorkbook for XLSX-, XLSM-, og malvarianter; hvilken som helst av dem kan allerede være åpen et annet sted i applikasjonen, og fremviseren leser bare fra den. Når kontrollen selv skal eie filen, inspiserer LoadFromFile filendelsen, ruter XLSX, XLSM, XLTX, XLTM, og ODS gjennom den moderne motoren og alt annet gjennom den klassiske, og frigjør hvilken arbeidsbok den enn opprettet så snart kontrollen tømmes eller ødelegges

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;

Å finne riktig celle inne i et sammenslått område

Å løse et klikk til riktig celle i TXLSWorkbookViewer er et to-trinns oppslag, og oppdelingen betyr noe fordi pikselgeometri og regnearksemantikk er genuint forskjellige problemer. Det første trinnet er ren geometri: en privat CellAtPoint-metode går gjennom kolonnebredder og radhøyder fra den gjeldende rulleposisjonen til den finner båndet som inneholder den klikkede X- og Y-koordinaten, uten noen bevissthet om sammenslåtte celler i det hele tatt. Det andre trinnet er semantisk: hver vei som endrer valget, et museklikk, en piltast, Tab, eller et direkte kall til GoToCell, kanaliseres gjennom én intern ChangeSelection-rutine, som normaliserer den rå raden og kolonnen mot enhver sammenslåing og fester dem til sammenslåingens ankercelle før valget faktisk endres

Ankeret er den øvre-venstre cellen i det sammenslåtte området, og det er den eneste cellen i den blokken som genuint holder en verdi, et format, en kommentar, eller en hyperlenke i den underliggende arbeidsbok-modellen; hver annen celle sammenslåingen visuelt dekker, er tom i selve dataen. For klassiske XLS-arbeidsbøker kommer ankeret fra Cell.MergeArea, en IXLSRange hvis Row og Column peker på den eiende cellen; for XLSX- og ODS-arbeidsbøker returnerer MergedCells.FindAt en TXLSXMergedRange som eksponerer det samme ankeret som Row1 og Col1. Tegning løser et tilsvarende problem uavhengig, ved å utvide en sammenslått celles rektangel til sitt fulle rad- og kolonnespenn og hoppe over cellene inne i det spennet, slik at valgomrisset omslutter hele den sammenslåtte blokken snarere enn bare ankerhjørnet, og å skrive sammenslåtte layouter i stedet for bare å lese dem tilbake, er et beslektet, men distinkt problem dekket i følgeartikkelen om sammenslått-celle-layout for rapportmaler

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;

Hva holder rulling, hoder, og valg synkronisert?

TXLSWorkbookViewer holder tre separate biter av tilstand koherente: den logiske rulleposisjonen holdt i TopRow og LeftCol, de native Windows-rullefeltene kontrollen forespør gjennom WS_HSCROLL og WS_VSCROLL i CreateParams, og det gjeldende valget i SelectedRow og SelectedCol. Å dra et rullefelt eller spinne musehjulet utløser WM_HSCROLL, WM_VSCROLL, eller WM_MOUSEWHEEL, som oppdaterer TopRow eller LeftCol og tegner på nytt; valget flytter seg ikke, noe som stemmer med hvordan Excel selv skiller panorering fra å velge. Etter noen av de oppdateringene skyver UpdateScrollBars den nye posisjonen tilbake inn i det native rullefeltet gjennom SetScrollInfo, slik at glidebryteren aldri driver ut av samsvar med det rutenettet faktisk viser

Tastaturnavigasjon kjører den samme synkroniseringen i motsatt retning: å flytte valget forbi kanten av det synlige rutenettet kaller EnsureSelectionVisible, som dytter TopRow eller LeftCol ved å akkumulere faktiske kolonnebredder og radhøyder i stedet for bare å øke med én, ettersom rader og kolonner kan bære egendefinerte størrelser, og kaller deretter UpdateScrollBars slik at glidebryteren reflekterer hvor tastaturet nettopp tok visningen. Rad-nummer- og kolonne-bokstav-hodebåndene, dimensjonert gjennom RowHeaderWidth og ColumnHeaderHeight, er delen av denne kontrollen som forblir fast på skjermen mens TopRow og LeftCol ruller dataen under, og det er omfanget av fastfrysing denne kontrollen gjør på egen hånd: det er ikke Excels Fastlås ruter-funksjon, og det finnes ingen innebygd måte å feste en vilkårlig datarad eller -kolonne mens resten av arket ruller forbi den. Én grense verdt å teste før man sender ut en fremviser over filer man ikke fullstendig kontrollerer, er at TopRow og LeftCol ikke klemmes mot regnearkets faktiske brukte område, så en glidebryter dratt til sin strukturelle grense kan lande på rad 1 048 576 eller kolonne 16 384 og vise et tomt rutenett i stedet for den siste raden eller kolonnen som faktisk holder data; arbeidsbøker store nok til å gjøre det merkbart, er vanligvis også store nok til å trenge oppmerksomheten på innlastingssiden dekket i stor-arbeidsbok-ytelse-artikkelen

Å koble kommentarer og hyperlenker til mus- og valghendelser

TXLSWorkbookViewer behandler kommentarer og hyperlenker som attributter til hvilken celle som enn er valgt for øyeblikket, snarere enn som hover-mål, så SelectedCellCommentText, SelectedCellCommentAuthor, og SelectedCellHyperlink oppdateres hver gang OnSelectionChange utløses, uansett om valget flyttet seg ved museklikk, piltast, eller et kall til GoToCell. En kommentert celle får en liten rød trekant tegnet i sitt øvre-høyre hjørne som et visuelt hint, likt Excels eget kommentarflagg, men den markøren er rent visuell; det finnes ingen hover-utløst verktøytips innebygd i kontrollen, så en applikasjon som ønsker en popup ved museover i stedet for ved valg, må bygge det laget selv. Hyperlenke-aktivering fungerer på samme valg-først-måte: å dobbeltklikke en celle kaller ActivateSelectedCell, som leser SelectedCellHyperlink og, hvis den ikke er tom, utløser OnHyperlinkClick med måladressen og en var Handled: Boolean-parameter for håndtereren å sette

Hva OnHyperlinkClick ikke gjør, er like viktig: TXLSWorkbookViewer kaller aldri ShellExecute eller åpner en nettleser på egen hånd, uansett om håndtereren setter Handled til sann eller lar den stå usann. Navigasjon, og enhver avgjørelse om hva som teller som et trygt mål, er fullstendig vertsapplikasjonens ansvar, noe som er det riktige standardvalget for en komponent som ikke har noen anelse om den er innebygd i et pålitelig internt verktøy eller en fremviser for filer en kunde nettopp lastet opp

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;

Valgomfang og grenser for tastaturnavigasjon

Valg i TXLSWorkbookViewer er alltid én enkelt logisk celle, sporet som SelectedRow og SelectedCol; det finnes ingen rektangulær multi-celle-områdevalg i grunnkontrollen, så enhver funksjon som trenger å handle på en blokk med celler, må bygges over den snarere enn leses av et valgobjekt. Tastaturdekningen er bevisst grunnleggende: piltaster flytter én celle om gangen, Home returnerer til starten av raden eller, med Ctrl, til celle A1, Page Up og Page Down hopper ti rader, og Tab og Shift+Tab stepper på tvers av kolonner; det finnes ingen Ctrl+pil-hopp til kanten av et dataområde og intet Shift-utvidet områdevalg, så brukere som kommer rett fra Excel vil merke gapet på et tett ark

Kolonnegrenser håndheves ved det samme ChangeSelection-flaskehalspunktet som håndterer sammenslåingsnormalisering, og de skiller seg bevisst per motor: en fremviser bundet til en klassisk TXLSWorkbook klemmer ved kolonne 256, det strukturelle taket til BIFF8-formatet, mens en bundet til TXLSXWorkbook respekterer den moderne 16 384-kolonne-grensen XLSX arvet fra Excel 2007 og fremover. Rader er begrenset til 1 048 576 uansett, så den praktiske forskjellen mellom å åpne en eldre XLS-fil og en XLSX-fil i den samme fremviseren, handler fullstendig om hvor langt til høyre rutenettet er villig til å la deg gå

Ingenting av dette er eksotisk når det først brytes ned til piksel-oppslag, ankernormalisering, og en håndfull meldingshåndterere, men å få de tre til å bli enige under ekte filer, med ekte sammenslåinger, kommentarer, og hyperlenker, er mesteparten av arbeidet i en slik komponent. TXLSWorkbookViewer følger med som en del av den standard HotXLS Excel-komponenten for Delphi og C++Builder, sammen med de klassiske og XLSX-objektmodellene den gjengir fra