HotXLS čuva selekcije radnog lista i pozicije skrolovanja po pane-u kroz jedan pane-aware API na oba, TXLSWorksheet i TXLSXWorksheet: SelectAreas, GetSelectedAreas, ScrollWindow i TryGetWindowScroll. Za klasične .xls fajlove, HotXLS zapisuje BIFF8 Selection record-e (0x001D) sa najviše 1369 oblasti svaki, prevodi logička imena pane-ova u pane bajtove koje format definiše, i drži svaku scroll osu na Window2 ili Pane record-u gde je Excel očekuje
Problem se obično pokaže u alatu za usaglašavanje ili audit. Alat otavi izvoz glavne knjige, nađe svaku ćeliju koja ne odstupa od izvornog sistema, i sačuva workbook sa tim ćelijama već selektovanim ispod zamrznutog reda zaglavlja, pa revizor doskoči na razlike umesto da ih skroluje. Sa četrdeset razlika radi lepo. Fajl za kraj meseca ima 3.000, i jedan Selection record sa 3.000 oblasti ne može da postoji: njegovo telo bi tražilo 18.009 bajtova, više nego dvostruko od onoga što jedan BIFF8 record može da ponese. Scroll pozicija ima sličnu zamku. Na listu sa zamrznutim pane-ovima, „gde je korisnik gledao“ su četiri pane-a koja dele dve pozicije redova i dve pozicije kolona, a ne jedan koordinat
Zašto velika selekcija treba više od jednog Selection record-a?
Velika selekcija treba više record-a jer je telo BIFF8 record-a ograničeno na 8224 bajta, a svaka selektovana oblast košta fiksnih šest bajtova. [MS-XLS] §2.4.248 razlaže Selection record kao fiksni deo od 9 bajtova (pane bajt, rwAct i colAct za aktivnu ćeliju, irefAct za aktivnu oblast i cref za broj oblasti) iza kojeg ide cref RefU struktura, svaka sa dva 16-bitna reda i dve 8-bitne kolone. Najveći broj koji staje je (8224 − 9) / 6 zaokružen nadole, što je 1369, i to daje telo od 8223 bajta, jedan bajt ispod granice. TXLSWorksheet.StoreSelectionGroup koristi tu konstantu kao MaxAreasPerRecord i zapisuje veću grupu kao uzastopne Selection record-e za isti pane, po 1369 oblasti
Detalj koji ujede je irefAct. Svaki chunk ponavlja isti aktivni red, aktivnu kolonu i indeks aktivne oblasti, a irefAct indeksira agregiranu sekvencu svih chunk-ova, ne oblasti unutar record-a koji ga nosi. Selekcija od jedne oblasti više nego granica ovo čini konkretnim: 1370 oblasti sa poslednjom aktivnom postaje dva record-a, prvi sa cref 1369 i drugi sa cref 1, i oba nose irefAct 1369. Ta vrednost je veća od sopstvenog broja oblasti drugog record-a. Čitač koji proverava irefAct prema cref u svakom record-u odbacuje ispravan fajl, a čitač koji menja svoje stanje na svakom record-u gubi prvih 1369 oblasti. HotXLS čitač nadovezuje uzastopne record-e istog pane-a u jednu grupu, traži da se svaki chunk složi oko aktivne ćelije i indeksa, i proveru opsega vrši tek na EOF record-u radnog lista, kad je cela sekvenca poznata. Pane-first SelectAreas overload zato nema plafon od 1369 oblasti. Validira svaku A1 referencu i aktivni indeks pre nego što uzme write lock radnog lista, i vraća False sa prethodnom selekcijom netaknutom ako je bilo šta neispravno
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
Diffs: TXLSSelectedAreas;
I: Integer;
begin
Book := TXLSWorkbook.Create;
try
Sheet := Book.Sheets.Add;
Sheet.FreezePanes(1, 1); // red zaglavlja i kolona A ostaju na mestu
SetLength(Diffs, 3000);
for I := 0 to High(Diffs) do
Diffs[I] := Format('C%d', [I + 2]);
// Zamrzavanje resetuje sačuvanu selekciju, pa selektuj posle zamrzavanja.
// 3000 oblasti se čuva kao tri Selection record-a: 1369 + 1369 + 262
if not Sheet.SelectAreas(xlspBottomRight, Diffs, 0) then
raise Exception.Create('Selection rejected');
Book.SaveAs('reconciliation.xls');
finally
Book.Free;
end;
end;
Koji pane bajt koristi Selection record?
Selection record identifikuje svoj pane numeričkim kodom koji format definiše: 0 za donji desni, 1 za gornji desni, 2 za donji levi i 3 za gornji levi. Javna enumeracija TXLSPanePosition deklarisana je u redosledu čitanja, xlspTopLeft, xlspTopRight, xlspBottomLeft, xlspBottomRight, pa je Ord(xlspTopLeft) 0, što je donji desni pane u fajlu. Direktno kastovanje enumeracije u pane bajt upisalo bi svaku gornju levu selekciju na donji desni pane bez ijedne greške. Svaki pane-aware HotXLS ulaz punkt konvertuje enumeraciju kroz eksplicitnu case naredbu, pa pozivaoci nikada ne rade sa numeričkim kodovima. Postojanje pane-a se takođe proverava: gornji desni pane postoji samo uz vertikalni split, donji levi samo uz horizontalni, a donji desni samo uz oba. Za pane koji trenutna geometrija split-a ili zamrzavanja nema, SelectAreas vraća False, a GetSelectedAreas vraća prazan niz sa ActiveAreaIndex postavljenim na -1, bez pravljenja pane-a, objekta selekcije ili ćelije u workbook-u
Gde živi scroll pozicija svakog pane-a?
Scroll pozicija svakog pane-a rasprsuta je po dva record-a, jer četiri pane-a dele samo dve pozicije redova i dve pozicije kolona. U klasičnom workbook-u, prvi vidljivi red gornjih pane-ova i prva vidljiva kolona levih pane-ova su Window2.rwTop i Window2.colLeft, dok su red donjih pane-ova i kolona desnih pane-ova Pane.rwTop i Pane.colLeft. ScrollWindow(xlspTopRight, R, C) zato zapisuje Window2.rwTop i Pane.colLeft, i postavljanje kolone gornjeg desnog pane-a pomera i donji desni pane, isto kao što ta dva dele jedan horizontalni scrollbar u Excelu. Javne metode koriste redove i kolone sa bazom 1. Nepostojeći pane vraća False i oba izlaza upita postavlja na nulu, a koordinata van opsega se odbija pre nego što bilo koja osa promeni. Ovde ništa ne zavisi od toga kako viewer iscrtava grid. Rendering kontrola drži svoj TopRow i LeftCol, kao što opisuje članak o iscrtavanju workbook-ova u custom VCL gridu, i to je runtime stanje, ne ono što se čuva
XLSX iste podatke rasprsne po dva elementa: sheetView/@topLeftCell (ECMA-376 Part 1, §18.3.1.87) za ceo prozor i child pane/@topLeftCell (§18.3.1.66) za donju desnu stranu split-a. Oba atributa mogu postojati istovremeno. HotXLS prvo čita spoljašnji atribut u polja nivoa prozora, dozvoljava pane child-u da pregazi samo polja nivoa pane-a, i oba zapisuje nazad odvojeno. Spajanje ta dva sloja u jedan je upravo način na koji gornja ili leva scroll pozicija tiho nestane pri učitavanju. Kopije radnih lista nose oba sloja u oba engine-a. Stariji ulazni punktovi zadržavaju originalno ponašanje: klasična svojstva ScrollRow i ScrollColumn, i zero-based XLSX SetPaneScroll i GetPaneScroll. Samu geometriju zamrzavanja i split-a podešava se sheet-level podešavanjima pokrivenim u zaštiti lista, podešavanju strane i štampanju
var
Row, Col: Integer;
begin
Sheet.FreezePanes(1, 1);
// Donji desni: donja osa redova (Pane.rwTop) i desna osa kolona (Pane.colLeft)
Sheet.ScrollWindow(xlspBottomRight, 500, 3);
// Gornji desni dele desnu osu kolona, pa ovo pomera i donji desni na kolonu 6
Sheet.ScrollWindow(xlspTopRight, 1, 6);
if Sheet.TryGetWindowScroll(xlspBottomRight, Row, Col) then
Memo1.Lines.Add(Format('Bottom-right starts at row %d, column %d', [Row, Col]));
// Donji desni počinje na redu 500, koloni 6
end;
Šta se dešava kad je Selection record pokvaren?
Kad je Selection record pokvaren, HotXLS ga čuva kao neprozirne bajtove, prijavljuje dijagnostički kod 1304 (xlsDiagnosticSelectionRecordInvalid) i pri čuvanju upisuje originalno telo nazad bajt po bajt. Pre nego što record uđe u grupu svog pane-a, čitač ga proverava redom. Pane bajt mora biti 3 ili manje. Record-i za jedan pane moraju biti kontinualni u stream-u. Fiksnih 9 bajtova mora biti prisutno. cref mora biti između 1 i 1369, a telo mora biti tačno 9 + cref × 6 bajtova dugačko. Svaki chunk u grupi mora se slagati oko aktivne ćelije i irefAct, irefAct ne sme imati postavljen znakovni bit, aktivna kolona mora biti na gridu, i nijedna oblast ne sme imati obrnute granice. Problemi u jednom fizičkom record-u prijavljuju se jednom po record-u. Protivrečnosti koje se pojave tek posle agregacije, poput irefAct koji pokazuje iza ukupnog broja oblasti ili aktivne ćelije van indeksirane oblasti, prijavljuju se jednom po grupi na EOF. Neispravna grupa ostaje nevidljiva tipiziranom API-ju: GetSelectedAreas vraća prazan niz sa indeksom -1 za taj pane, dok svaki drugi pane nastavlja da radi
var
I: Integer;
D: TXLSDiagnostic;
begin
if Book.Open('supplier-upload.xls') <> 1 then
Exit;
for I := 0 to Book.Diagnostics.Count - 1 do
begin
D := Book.Diagnostics[I];
if D.Code = xlsDiagnosticSelectionRecordInvalid then
Log.Add(Format('%s: record $%.4x kept opaque (%s)',
[D.SheetName, D.RecordId, D.Message]));
end;
end;
Kako selekcije prežive umetanje redova i kolona?
Selekcije prežive strukturne izmene jer umetanje ili brisanje celih redova ili kolona remapira svaku predstavljenu pane grupu u oba engine-a, klasičnom i XLSX, kroz jedan zajednički remapper. Preživele oblasti zadržavaju redosled, a aktivna oblast zadržava identitet. Ako je aktivna oblast obrisana, prva preživela sledbenica postaje aktivna, pa poslednja preživela prethodnica ako ništa ne sledi. Ako je svaka oblast obrisana, grupa se skuplja na jednu ćeliju na granici brisanja, a aktivna ćelija koja više ne upada u izabranu oblast pomera se u njen gornji levi ugao, pa se indeks i koordinata nikada ne protivreče. Granice su namerne. Neispravne klasične grupe remapper preskače umesto da ih preslika u izmišljenu selekciju, pa njihovi originalni bajtovi i dalje prođu kroz upis i čitanje netaknuti. Izmena jednog pane-a zamenjuje samo record-e tog pane-a i ostavlja ostale bajt-identične. ODS uopšte ne dobija pane selection stanje, jer ODF nema ekvivalentnu strukturu prikaza radnog lista da ga nosi
Ako Vaša aplikacija zapisuje .xls fajlove koje korisnici otvaraju i kroz koje treba da se kreću, bilo da pregledaju označene ćelije, nastave tamo gde su stali ili podele zamrznuti dashboard, pane-aware API za selekciju i skrolovanje deo je HotXLS Delphi spreadsheet komponente, i radi isto za XLS i XLSX