Tehnički članak

Prilagođena mreža proračunske tablice u Delphiju uz HotXLS

HotXLS isporučuje TXLSWorkbookViewer, nativnu VCL kontrolu koja radne knjige XLS, XLSX, XLSM i ODS prikazuje kao interaktivnu mrežu proračunske tablice unutar Delphi ili C++Builder obrasca, bez instalacije Excela i bez upravljanja njime putem OLE automatizacije. Kvalitetna izrada takve kontrole zahtijeva rješavanje triju konkretnih problema: povezivanje klika mišem unutar spojene ćelije s ispravnom logičkom ćelijom, održavanje usklađenih položaja pomicanja, zaglavlja i odabira ćelije dok korisnik pomiče list mnogo veći od vidljivog prozora te određivanje stvarne radnje klika na oznaku komentara ili ćeliju s hipervezom

Većina Delphi timova poseže za preglednikom proračunskih tablica iz razloga koji nemaju veze s uređivanjem: revizijska stanica koja pregleda prenesene radne knjige prije ulaska u obradu, kiosk ili preglednik izvještaja u kojem Microsoft Office nije dio instalacije ili alat za osiguranje kvalitete koji mora prikazati sadržaj radne knjige bez nepredvidivosti upravljanja stvarnim Excelovim procesom preko COM-a. Obična mreža nizova brzo prikaže tekst u ćelijama, ali datoteka proračunske tablice nije obična mreža: ćelije se spajaju u blokove koji postoje tek u temeljnom modelu, listovi imaju fiksne trake zaglavlja i neovisne vodoravne i okomite položaje pomicanja, a pojedinačne ćelije sadrže komentare i hiperveze koje trebaju vlastiti model interakcije. TXLSWorkbookViewer odgovor je HotXLS-a na taj nedostatak, a njegov unutarnji dizajn razuman je predložak za svakoga tko od nule izrađuje sličnu kontrolu

Kako preglednik radne knjige izbjegava ovisnost o Excelu

TXLSWorkbookViewer u potpunosti izbjegava Excel jer čita vlastiti analizirani objektni model HotXLS-a umjesto da dokument otvara kroz Excel i upravlja njime. Svojstvo Workbook povezuje postojeći TXLSWorkbook za klasične XLS datoteke, a XlsxWorkbook povezuje TXLSXWorkbook za XLSX, XLSM i inačice predložaka; bilo koja radna knjiga već može biti otvorena drugdje u aplikaciji, a preglednik je samo čita. Kada sama kontrola treba preuzeti vlasništvo nad datotekom, LoadFromFile provjerava nastavak, usmjerava XLSX, XLSM, XLTX, XLTM i ODS kroz suvremeni mehanizam, a sve ostalo kroz klasični, zatim oslobađa onu radnu knjigu koju je stvorila nakon čišćenja ili uništenja kontrole

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;

Pronalaženje ispravne ćelije unutar spojenog raspona

Razrješenje klika na ispravnu ćeliju u TXLSWorkbookViewer-u odvija se u dva koraka, a ta je podjela važna jer su geometrija piksela i semantika proračunske tablice doista različiti problemi. Prvi je korak čista geometrija: privatna metoda CellAtPoint prolazi kroz širine stupaca i visine redaka od trenutačnog položaja pomicanja dok ne pronađe traku koja sadrži kliknute X i Y koordinate, bez ikakve svijesti o spojenim ćelijama. Drugi je korak semantički: svaki put koji mijenja odabir, klik mišem, tipka sa strelicom, Tab ili izravan poziv GoToCell, prolazi kroz jednu internu rutinu ChangeSelection, koja normalizira sirovi redak i stupac prema svakom spajanju te ih prije promjene odabira pomiče na sidrenu ćeliju spajanja

Sidro je gornja lijeva ćelija spojenog raspona i jedina je ćelija u tom bloku koja u temeljnom modelu radne knjige doista sadrži vrijednost, oblikovanje, komentar ili hipervezu; sve druge ćelije koje spajanje vizualno pokriva u samim su podacima prazne. Kod klasičnih XLS radnih knjiga sidro dolazi iz Cell.MergeArea, objekta IXLSRange čiji Row i Column upućuju na vlasničku ćeliju; kod XLSX i ODS radnih knjiga MergedCells.FindAt vraća TXLSXMergedRange koji isto sidro izlaže kroz Row1 i Col1. Crtanje zasebno rješava jednak problem tako što pravokutnik spojene ćelije proširuje na cijeli raspon redaka i stupaca te preskače ćelije unutar tog raspona, pa se okvir odabira proteže oko cijelog spojenog bloka, a ne samo oko njegova kuta sa sidrom, dok je zapisivanje spojenih rasporeda, za razliku od njihova čitanja, povezan ali zaseban problem obrađen u pratećem članku o rasporedu spojenih ćelija za predloške izvještaja

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;

Što održava usklađenost pomicanja, zaglavlja i odabira

TXLSWorkbookViewer održava usklađenima tri zasebna dijela stanja: logički položaj pomicanja u TopRow i LeftCol, nativne Windowsove klizače koje kontrola traži kroz WS_HSCROLL i WS_VSCROLL u CreateParams te trenutačni odabir u SelectedRow i SelectedCol. Povlačenje klizača ili okretanje kotačića miša pokreće WM_HSCROLL, WM_VSCROLL ili WM_MOUSEWHEEL, koji ažuriraju TopRow ili LeftCol i ponovno crtaju prikaz; odabir se ne pomiče, što odgovara načinu na koji i sam Excel odvaja pomicanje od odabira. Nakon bilo kojeg od tih ažuriranja UpdateScrollBars vraća novi položaj u nativni klizač kroz SetScrollInfo, pa klizač nikada ne izgubi usklađenost s onime što mreža stvarno prikazuje

Navigacija tipkovnicom provodi istu sinkronizaciju u suprotnom smjeru: kada se odabir pomakne iza ruba vidljive mreže, poziva se EnsureSelectionVisible, koji povećava TopRow ili LeftCol zbrajanjem stvarnih širina stupaca i visina redaka umjesto jednostavnog povećanja za jedan, jer retci i stupci mogu imati prilagođene veličine, a zatim poziva UpdateScrollBars kako bi klizač odražavao mjesto na koje je tipkovnica upravo odvela prikaz. Trake zaglavlja s brojevima redaka i slovima stupaca, određene kroz RowHeaderWidth i ColumnHeaderHeight, dio su kontrole koji ostaje fiksiran na zaslonu dok TopRow i LeftCol pomiču podatke ispod njega, a to je ujedno opseg zamrzavanja koji ova kontrola samostalno pruža: to nije Excelova značajka Freeze Panes i ne postoji ugrađen način za pričvršćivanje proizvoljnog retka ili stupca podataka dok ostatak lista prolazi pokraj njega. Prije isporuke preglednika za datoteke nad kojima nemate potpunu kontrolu vrijedi ispitati i jednu granicu: TopRow i LeftCol nisu ograničeni prema stvarno korištenom rasponu radnog lista, pa klizač povučen do svoje strukturne granice može završiti na retku 1,048,576 ili stupcu 16,384 i prikazati praznu mrežu umjesto posljednjeg retka ili stupca koji doista sadrži podatke; radne knjige dovoljno velike da se to primijeti obično su dovoljno velike i za probleme pri učitavanju opisane u članku o performansama velikih radnih knjiga

Povezivanje komentara i hiperveza s događajima miša i odabira

TXLSWorkbookViewer tretira komentare i hiperveze kao svojstva trenutačno odabrane ćelije, a ne kao ciljeve pri prelasku pokazivačem, pa se SelectedCellCommentText, SelectedCellCommentAuthor i SelectedCellHyperlink ažuriraju svaki put kada se aktivira OnSelectionChange, bez obzira na to je li se odabir pomaknuo klikom miša, tipkom sa strelicom ili pozivom GoToCell. Na komentiranoj ćeliji u gornjem se desnom kutu crta mali crveni trokut kao vizualni znak, slično Excelovoj oznaci komentara, ali ta je oznaka samo vizualna; kontrola nema ugrađen opisni oblačić koji se pokreće prelaskom pokazivača, pa aplikacija koja želi skočni prozor pri prelasku mišem umjesto pri odabiru mora sama izgraditi taj sloj. Aktivacija hiperveze radi na isti način koji najprije odabire ćeliju: dvostruki klik poziva ActivateSelectedCell, koji čita SelectedCellHyperlink i, ako nije prazan, pokreće OnHyperlinkClick s ciljnom adresom i parametrom var Handled: Boolean koji rukovatelj treba postaviti

Jednako je važno što OnHyperlinkClick ne radi: TXLSWorkbookViewer nikada sam ne poziva ShellExecute niti otvara preglednik, bez obzira na to postavi li rukovatelj Handled na true ili ga ostavi false. Navigacija i svaka odluka o tome što se smatra sigurnim ciljem u potpunosti su odgovornost aplikacije domaćina, što je isprana zadana vrijednost za komponentu koja ne zna je li ugrađena u pouzdani interni alat ili preglednik datoteka koje je korisnik upravo prenio

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;

Ograničenja opsega odabira i navigacije tipkovnicom

Odabir u TXLSWorkbookViewer-u uvijek je jedna logička ćelija, praćena kroz SelectedRow i SelectedCol; osnovna kontrola nema pravokutni odabir više ćelija, pa se svaka značajka koja mora djelovati nad blokom ćelija mora izgraditi iznad nje umjesto čitanja objekta odabira. Podrška tipkovnice namjerno je osnovna: tipke sa strelicama pomiču se jednu ćeliju, Home vraća na početak retka ili, uz Ctrl, u ćeliju A1, Page Up i Page Down skaču deset redaka, a Tab i Shift+Tab pomiču se kroz stupce; nema skoka Ctrl+strelica do ruba podatkovnog područja ni odabira raspona proširenog tipkom Shift, pa će korisnici koji dolaze izravno iz Excela primijetiti taj nedostatak na gustom listu

Ograničenja stupaca provjeravaju se na istoj ulaznoj točki ChangeSelection koja obrađuje normalizaciju spajanja, a namjerno se razlikuju prema mehanizmu: preglednik povezan s klasičnim TXLSWorkbook-om ograničava se na stupac 256, strukturni maksimum formata BIFF8, dok onaj povezan s TXLSXWorkbook-om poštuje suvremeno ograničenje od 16,384 stupca koje je XLSX naslijedio od Excela 2007 nadalje. Retci su u oba slučaja ograničeni na 1,048,576, pa se praktična razlika između otvaranja naslijeđene XLS datoteke i XLSX datoteke u istom pregledniku svodi na to koliko daleko udesno mreža dopušta pomicanje

Ništa od toga nije egzotično kada se rastavi na pretraživanje piksela, normalizaciju sidra i nekoliko rukovatelja porukama, ali usklađivanje ta tri dijela sa stvarnim datotekama, stvarnim spojenim ćelijama, komentarima i hipervezama čini većinu posla u komponenti poput ove. TXLSWorkbookViewer dio je standardne komponente HotXLS Excel za Delphi i C++Builder, zajedno s klasičnim i XLSX objektnim modelima iz kojih prikazuje podatke