Techninis straipsnis

Individualus skaičiuoklės tinklelis Delphi aplinkoje su HotXLS

HotXLS pateikia TXLSWorkbookViewer – savąjį VCL valdiklį, kuris XLS, XLSX, XLSM ir ODS darbaknyges atvaizduoja kaip interaktyvų skaičiuoklės tinklelį Delphi arba C++Builder formoje, neįdiegiant Excel ir nevaldant jo per OLE automatizavimą. Norint tinkamai sukurti tokį valdiklį, reikia išspręsti tris konkrečias problemas: susieti pelės paspaudimą, patekusį į sulietą langelį, su teisingu loginiu langeliu, išlaikyti slinkimo padėtį, antraščių juostas ir langelio pasirinkimą suderintus, kai naudotojas slenka gerokai už matomo lango didesnį lapą, ir nuspręsti, ką iš tikrųjų turi atlikti paspaudimas ant komentaro žymeklio arba langelio su hipersaitu

Dauguma Delphi komandų renkasi skaičiuoklės peržiūros programą dėl priežasčių, nesusijusių su redagavimu: audito stotelei, kuri peržiūri įkeltas darbaknyges prieš joms patenkant į apdorojimo grandinę, kioskui arba ataskaitų peržiūros programai, kur Microsoft Office nėra diegimo atvaizde, ar QA įrankiui, kuriam reikia parodyti darbaknygės turinį be nenuspėjamumo, būdingo tikram Excel procesui automatizuoti per COM. Paprastas eilučių ir stulpelių tinklelis leidžia greitai gauti tekstą langeliuose, tačiau skaičiuoklės failas nėra paprastas tinklelis: langeliai susilieja į blokus, kurie pagrindiniame modelyje atsiranda tik suliejus, lapai turi fiksuotas antraščių juostas ir nepriklausomas horizontaliojo bei vertikaliojo slinkimo padėtis, o atskiri langeliai turi komentarus ir hipersaitus, kuriems reikia atskiro sąveikos modelio. TXLSWorkbookViewer yra HotXLS atsakymas į šią spragą, o jo vidinis dizainas yra pagrįstas pavyzdys kiekvienam, nuo pradžių kuriančiam panašų valdiklį

Kaip darbaknygės peržiūros programa išvengia priklausomybės nuo Excel?

TXLSWorkbookViewer visiškai išvengia Excel, nes skaito per HotXLS nuosavą išanalizuotą objektų modelį, o ne atveria dokumentą per Excel ir valdo jį imituodamas naudotojo veiksmus. Savybė Workbook susieja esamą TXLSWorkbook su klasikiniais XLS failais, o XlsxWorkbook susieja TXLSXWorkbook su XLSX, XLSM ir šablonų variantais; bet kuris iš jų gali būti jau atvertas kitoje programos vietoje, o peržiūros programa tik iš jo skaito. Kai valdiklis turi pats valdyti failą, LoadFromFile patikrina plėtinį, nukreipia XLSX, XLSM, XLTX, XLTM ir ODS per šiuolaikinį variklį, o visa kita – per klasikinį, ir atlaisvina tą darbaknygę, kurią sukūrė, kai valdiklis išvalomas arba sunaikinamas

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;

Kaip rasti tinkamą langelį sulietame diapazone?

Paspaudimo susiejimas su teisingu TXLSWorkbookViewer langeliu yra dviejų etapų paieška, o šis atskyrimas svarbus, nes pikselių geometrija ir skaičiuoklės semantika iš tikrųjų yra skirtingos problemos. Pirmasis etapas yra gryna geometrija: privatus metodas CellAtPoint eina per stulpelių pločius ir eilučių aukščius nuo dabartinės slinkimo padėties, kol randa juostą, kurioje yra paspaustas X ir Y koordinates, visiškai neatsižvelgdamas į sulietus langelius. Antrasis etapas yra semantinis: kiekvienas kelias, pakeičiantis pasirinkimą – pelės paspaudimas, rodyklės klavišas, Tab arba tiesioginis GoToCell iškvietimas – pereina per vieną vidinę ChangeSelection procedūrą, kuri normalizuoja neapdorotą eilutę ir stulpelį pagal bet kokį suliejimą ir prieš faktiškai pakeisdama pasirinkimą perkelia juos į suliejimo atraminį langelį

Atraminis langelis yra viršutinis kairysis sulieto diapazono langelis, ir tik jis pagrindiniame darbaknygės modelyje iš tikrųjų saugo reikšmę, formatą, komentarą arba hipersaitą; visi kiti langeliai, kuriuos vizualiai apima suliejimas, pačiuose duomenyse yra tušti. Klasikinėse XLS darbaknygėse atrama gaunama iš Cell.MergeArea, IXLSRange, kurio Row ir Column nurodo savąjį langelį; XLSX ir ODS darbaknygėse MergedCells.FindAt grąžina TXLSXMergedRange, pateikiantį tą pačią atramą per Row1 ir Col1. Piešimas atskirai sprendžia lygiavertę problemą: išplečia sulieto langelio stačiakampį per visą jo eilučių ir stulpelių aprėptį bei praleidžia į ją patenkančius langelius, todėl pasirinkimo kontūras apgaubia visą sulietą bloką, o ne tik jo atraminį kampą; sulietų išdėstymų rašymas, o ne tik jų skaitymas, yra susijusi, bet atskira problema, aptariama papildomame straipsnyje apie sulietų langelių išdėstymą ataskaitų šablonams

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;

Kas sinchronizuoja slinkimą, antraštes ir pasirinkimą?

TXLSWorkbookViewer išlaiko nuoseklias tris atskiras būsenos dalis: loginę slinkimo padėtį, laikomą TopRow ir LeftCol, savąsias Windows slinkties juostas, kurių valdiklis paprašo per WS_HSCROLL ir WS_VSCROLL funkcijoje CreateParams, ir dabartinį pasirinkimą, laikomą SelectedRow ir SelectedCol. Vilkdami slinkties juostą arba sukdami pelės ratuką sukeliame WM_HSCROLL, WM_VSCROLL arba WM_MOUSEWHEEL, kurie atnaujina TopRow arba LeftCol ir perpiešia vaizdą; pasirinkimas nepasislenka, taip atkartojant, kaip pats Excel atskiria slinkimą nuo pasirinkimo. Po bet kurio tokio atnaujinimo UpdateScrollBars per SetScrollInfo grąžina naują padėtį į savąją slinkties juostą, todėl slankiklis niekada nepraranda ryšio su tuo, ką iš tikrųjų rodo tinklelis

Naršymas klaviatūra vykdo tą pačią sinchronizaciją priešinga kryptimi: pasirinkimui perėjus už matomo tinklelio krašto, iškviečiamas EnsureSelectionVisible, kuris padidina TopRow arba LeftCol, susumuodamas tikruosius stulpelių pločius ir eilučių aukščius, o ne tiesiog padidindamas juos vienetu, nes eilutės ir stulpeliai gali turėti pasirinktinius dydžius, ir tada iškviečia UpdateScrollBars, kad slankiklis atspindėtų vietą, į kurią klaviatūra perkėlė vaizdą. Eilučių numerių ir stulpelių raidžių antraščių juostos, kurių dydžius nustato RowHeaderWidth ir ColumnHeaderHeight, yra šio valdiklio dalis, liekanti fiksuota ekrane, kol TopRow ir LeftCol slenka po ja esančiais duomenimis; tai yra visa fiksavimo funkcija, kurią šis valdiklis atlieka savarankiškai: tai nėra Excel funkcija Freeze Panes ir nėra įtaisytosios galimybės prisegti pasirinktą duomenų eilutę ar stulpelį, kol likęs lapas slenka pro šalį. Viena riba, kurią verta patikrinti prieš išleidžiant peržiūros programą failams, kurių visiškai nekontroliuojate, yra ta, kad TopRow ir LeftCol nėra apriboti pagal tikrąjį darbalapio naudojamą diapazoną, todėl iki struktūrinės ribos nuvilktas slankiklis gali atsidurti 1 048 576 eilutėje arba 16 384 stulpelyje ir rodyti tuščią tinklelį vietoj paskutinės eilutės ar stulpelio, kuriame iš tikrųjų yra duomenų; pakankamai didelėms darbaknygėms, kad tai būtų pastebima, paprastai taip pat reikia įkėlimo pusės dėmesio, aprašyto didelės darbaknygės našumo straipsnyje

Kaip susieti komentarus ir hipersaitus su pelės bei pasirinkimo įvykiais?

TXLSWorkbookViewer komentarus ir hipersaitus traktuoja kaip šiuo metu pasirinkto langelio atributus, o ne kaip užvedimo taškus, todėl SelectedCellCommentText, SelectedCellCommentAuthor ir SelectedCellHyperlink atnaujinami kiekvieną kartą suveikus OnSelectionChange, nesvarbu, ar pasirinkimas pakeistas pelės paspaudimu, rodyklės klavišu, ar iškvietus GoToCell. Komentaro langelio viršutiniame dešiniajame kampe nupiešiamas mažas raudonas trikampis kaip vaizdinė užuomina, panaši į paties Excel komentaro vėliavėlę, tačiau šis žymeklis yra tik vaizdinis; valdiklyje nėra užvedimu suaktyvinamo paaiškinimo langelio, todėl programai, norinčiai iššokančio lango užvedus pelę, o ne pasirinkus langelį, reikia pačiai sukurti šį sluoksnį. Hipersaito suaktyvinimas veikia taip pat, pirmiausia pasirenkant: dukart spustelėjus langelį iškviečiamas ActivateSelectedCell, kuris perskaito SelectedCellHyperlink ir, jei jis netuščias, su tiksliniu adresu sukelia OnHyperlinkClick, perduodamas var Handled: Boolean parametrą, kurį apdorojanti procedūra turi nustatyti

Ne mažiau svarbu ir tai, ko OnHyperlinkClick nedaro: TXLSWorkbookViewer niekada neiškviečia ShellExecute ir pats neatveria naršyklės, nesvarbu, ar apdorojanti procedūra nustato Handled į true, ar palieka jį false. Už naršymą ir bet kokį sprendimą, kas laikytina saugiu taikiniu, visiškai atsakinga pagrindinė programa; tai tinkama numatytoji elgsena komponentui, nežinančiam, ar jis įdėtas į patikimą vidinį įrankį, ar į peržiūros programą, skirtą ką tik kliento įkeltam failui

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;

Pasirinkimo aprėptis ir naršymo klaviatūra ribos

TXLSWorkbookViewer pasirinkimas visada yra vienas loginis langelis, sekamas per SelectedRow ir SelectedCol; baziniame valdiklyje nėra stačiakampio kelių langelių diapazono pasirinkimo, todėl bet kokią su langelių bloku susijusią funkciją reikia kurti virš jo, o ne gauti iš pasirinkimo objekto. Klaviatūros palaikymas sąmoningai paprastas: rodyklių klavišai perkelia vienu langeliu, Home grąžina į eilutės pradžią arba, kartu su Ctrl, į A1 langelį, Page Up ir Page Down peršoka dešimt eilučių, o Tab ir Shift+Tab pereina per stulpelius; nėra Ctrl+Arrow peršokimo į duomenų srities kraštą ir nėra su Shift išplečiamo diapazono pasirinkimo, todėl tiesiai iš Excel perėję naudotojai tankiame lape pastebės šį trūkumą

Stulpelių ribos taikomos tame pačiame ChangeSelection valdymo taške, kuris tvarko suliejimo normalizavimą, ir specialiai skiriasi pagal variklį: su klasikiniu TXLSWorkbook susietas peržiūros programos vaizdas apribojamas ties 256 stulpeliais, struktūrine BIFF8 formato riba, o su TXLSXWorkbook susietas vaizdas taiko šiuolaikinę 16 384 stulpelių ribą, kurią XLSX paveldėjo iš Excel 2007. Eilučių riba abiem atvejais yra 1 048 576, todėl praktinis skirtumas atveriant seną XLS failą ir XLSX failą toje pačioje peržiūros programoje priklauso tik nuo to, kiek toli į dešinę tinklelis leidžia nueiti

Nė vienas iš šių dalykų nėra egzotiškas, kai suskaidomas į paiešką pagal pikselius, atramos normalizavimą ir keletą pranešimų apdorojimo procedūrų, tačiau priversti visus tris veikti kartu su tikrais failais, tikrais suliejimais, komentarais ir hipersaitais yra didžiausia tokio komponento darbo dalis. TXLSWorkbookViewer pateikiamas kaip standartinio HotXLS Excel komponento, skirto Delphi ir C++Builder, dalis kartu su klasikiniais ir XLSX objektų modeliais, iš kurių jis atvaizduoja duomenis