HotXLS stochează selecțiile foilor de lucru și pozițiile de scroll per panou printr-un singur API conștient de panouri, atât pe TXLSWorksheet, cât și pe TXLSXWorksheet: SelectAreas, GetSelectedAreas, ScrollWindow și TryGetWindowScroll. Pentru fișierele .xls clasice, HotXLS scrie înregistrări BIFF8 Selection (0x001D) de cel mult 1369 de zone fiecare, convertește numele logice de panouri în byte-urile de panou pe care formatul le definește și ține fiecare axă de scroll pe înregistrarea Window2 sau Pane acolo unde Excel o așteaptă
Problema apare de obicei într-un instrument de reconciliere sau de audit. Instrumentul deschide un export de registru contabil, găsește fiecare celulă care nu se află la aceeași concluzie cu sistemul sursă și salvează registrul de lucru cu celulele acelea deja selectate sub un rând de antet înghețat, astfel încât revisorul aterizează direct pe diferențe în loc să le caute prin scroll. Cu patruzeci de diferențe merge perfect. Fișierul de închidere de lună are 3.000, iar o singură înregistrare Selection cu 3.000 de zone nu poate exista: corpul ei ar avea nevoie de 18.009 de byte, de peste două ori cât poate duce o înregistrare BIFF8. Poziția de scroll are o capcană asemănătoare. Pe o foaie cu panouri înghețate, „unde privea utilizatorul" înseamnă patru panouri care împart două poziții de rând și două de coloană, nu o singură coordonată
De ce are nevoie o selecție mare de mai mult de o înregistrare Selection?
O selecție mare are nevoie de mai multe înregistrări pentru că corpul unei înregistrări BIFF8 este plafonat la 8224 de byte, iar fiecare zonă selectată costă șase byte fix. [MS-XLS] §2.4.248 aranjează înregistrarea Selection ca o parte fixă de 9 byte (byte-ul de panou, rwAct și colAct pentru celula activă, irefAct pentru zona activă și cref pentru numărul de zone), urmată de cref structuri RefU, fiecare ținând două rânduri pe 16 biți și două coloane pe 8 biți. Cel mai mare număr care încape este (8224 − 9) / 6 rotunjit în jos, adică 1369, ceea ce produce un corp de 8223 de byte, un byte sub limită. TXLSWorksheet.StoreSelectionGroup folosește constanta aceea ca MaxAreasPerRecord și scrie un grup mai mare ca înregistrări Selection consecutive pentru același panou, câte 1369 de zone
Detaliul care mușcă este irefAct. Fiecare bucată repetă același rând activ, aceeași coloană activă și același index de zonă activă, iar irefAct indexează secvența agregată a tuturor bucăților, nu zonele din interiorul înregistrării care îl poartă. O selecție cu o zonă peste limită face totul concret: 1370 de zone cu ultima activă devin două înregistrări, prima cu cref 1369, a doua cu cref 1, iar ambele poartă irefAct 1369. Valoarea este mai mare decât numărul propriu de zone al înregistrării a doua. Un cititor care verifică irefAct față de cref în fiecare înregistrare respinge un fișier valid, iar unul care își înlocuiește starea la fiecare înregistrare pierde primele 1369 de zone. Cititorul HotXLS concatenează înregistrările consecutive ale aceluiași panou într-un singur grup, cere fiecărei bucăți să fie de acord asupra celulei active și a indexului și rulează verificarea de interval abia la înregistrarea EOF a foii de lucru, când secvența completă este cunoscută. Overload-ul SelectAreas cu panou în frunte nu are deci niciun plafon de 1369 de zone. El validează fiecare referință A1 și indexul activ înainte să ia blocarea de scriere a foii de lucru și întoarce False cu selecția anterioară neschimbată dacă ceva este malformat
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
Diffs: TXLSSelectedAreas;
I: Integer;
begin
Book := TXLSWorkbook.Create;
try
Sheet := Book.Sheets.Add;
Sheet.FreezePanes(1, 1); // rândul de antet și coloana A rămân pe loc
SetLength(Diffs, 3000);
for I := 0 to High(Diffs) do
Diffs[I] := Format('C%d', [I + 2]);
// Înghețarea resetează selecția stocată, deci selectați după înghețare.
// 3000 de zone se salvează ca trei înregistrări Selection: 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;
Ce byte de panou folosește o înregistrare Selection?
O înregistrare Selection își identifică panoul după codul numeric pe care formatul îl definește: 0 pentru dreapta-jos, 1 pentru dreapta-sus, 2 pentru stânga-jos și 3 pentru stânga-sus. Enumerația publică TXLSPanePosition este declarată în ordinea de citire, xlspTopLeft, xlspTopRight, xlspBottomLeft, xlspBottomRight, deci Ord(xlspTopLeft) este 0, ceea ce în fișier este panoul dreapta-jos. Un cast direct al enum-ului în byte-ul de panou ar scrie fiecare selecție stânga-sus pe panoul dreapta-jos fără nicio eroare. Fiecare punct de intrare HotXLS conștient de panouri convertește enum-ul printr-o instrucțiune case explicită, astfel încât apelanții nu se ating niciodată de codurile numerice. Se verifică și existența panoului: panoul dreapta-sus există doar cu o împărțire verticală, cel stânga-jos doar cu una orizontală, iar cel dreapta-jos doar cu ambele. Pentru un panou pe care geometria curentă de împărțire sau de înghețare nu îl are, SelectAreas întoarce False, iar GetSelectedAreas întoarce un tablou gol cu ActiveAreaIndex setat la -1, fără să creeze un panou, un obiect de selecție sau o celulă în registrul de lucru
Unde se află poziția de scroll a fiecărui panou?
Poziția de scroll a fiecărui panou este împărțită între două înregistrări, pentru că patru panouri împart doar două poziții de rând și două de coloană. Într-un registru de lucru clasic, primul rând vizibil al panourilor superioare și prima coloană vizibilă a panourilor din stânga sunt Window2.rwTop și Window2.colLeft, în timp ce rândul panourilor inferioare și coloana panourilor din dreapta sunt Pane.rwTop și Pane.colLeft. ScrollWindow(xlspTopRight, R, C) scrie prin urmare Window2.rwTop și Pane.colLeft, iar setarea coloanei panoului dreapta-sus mută și panoul dreapta-jos, exact cum cei doi împart o singură bară de scroll orizontală în Excel. Metodele publice folosesc numere de rând și de coloană pornind de la 1. Un panou lipsă întoarce False și setează ambele ieșiri ale interogării la zero, iar o coordonată în afara intervalului este respinsă înainte să se schimbe oricare axă. Nimic de aici nu depinde de cum pictează un viewer grila. Un control de randare își ține propriile TopRow și LeftCol, așa cum descrie articolul despre randarea registrelor de lucru într-o grilă VCL custom, iar acelea sunt stare la runtime, nu ceea ce se salvează
XLSX întinde aceleași date peste două elemente: sheetView/@topLeftCell (ECMA-376 Partea 1, §18.3.1.87) pentru fereastră ca întreg și copilul pane/@topLeftCell (§18.3.1.66) pentru partea din dreapta-jos a unei împărțiri. Ambele atribute pot fi prezente simultan. HotXLS citește întâi atributul exterior în câmpurile de nivel de fereastră, lasă copilul pane să suprascrie doar câmpurile de nivel de panou și le scrie înapoi separat. Prăbușirea celor două straturi într-unul singur este exact felul în care o poziție de scroll superioară sau din stânga dispare în tăcere la încărcare. Copiile foilor de lucru poartă ambele straturi în ambele motoare. Punctele de intrare mai vechi își păstrează comportamentul original: proprietățile clasice ScrollRow și ScrollColumn, plus SetPaneScroll și GetPaneScroll din XLSX, cu bază zero. Geometria de înghețare și de împărțire în sine se configurează cu setările de nivel de foaie acoperite în protecția foilor, configurarea paginii și tipărirea
var
Row, Col: Integer;
begin
Sheet.FreezePanes(1, 1);
// Dreapta-jos: axa de rând inferioară (Pane.rwTop) și axa de coloană din dreapta (Pane.colLeft)
Sheet.ScrollWindow(xlspBottomRight, 500, 3);
// Dreapta-sus împarte axa de coloană din dreapta, deci aici se mută și dreapta-jos la coloana 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]));
// Dreapta-jos pornește de la rândul 500, coloana 6
end;
Ce se întâmplă când o înregistrare Selection este coruptă?
Când o înregistrare Selection este coruptă, HotXLS o păstrează ca byte opaci, raportează codul de diagnostic 1304 (xlsDiagnosticSelectionRecordInvalid) și rescrie corpul original byte cu byte la salvare. Înainte ca o înregistrare să intre în grupul panoului ei, cititorul o verifică în ordine. Byte-ul de panou trebuie să fie cel mult 3. Înregistrările unui panou trebuie să fie contigue în flux. Cei 9 byte fixi trebuie să fie prezenți. cref trebuie să fie între 1 și 1369, iar corpul trebuie să aibă exact 9 + cref × 6 byte. Fiecare bucată dintr-un grup trebuie să fie de acord asupra celulei active și a lui irefAct, irefAct nu trebuie să aibă bitul de semn setat, coloana activă trebuie să fie pe grilă și nicio zonă nu poate avea limite inversate. Problemele dintr-o singură înregistrare fizică se raportează o dată per înregistrare. Contradicțiile care apar abia după agregare, precum un irefAct care arată peste numărul total de zone sau o celulă activă în afara zonei indexate, se raportează o dată per grup la EOF. Un grup invalid rămâne invizibil pentru API-ul tipizat: GetSelectedAreas întoarce un tablou gol cu indexul -1 pentru panoul acela, în timp ce toate celelalte panouri continuă să funcționeze
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;
Cum supraviețuiesc selecțiile inserărilor de rânduri și coloane?
Selecțiile supraviețuiesc editărilor structurale pentru că inserarea sau ștergerea de rânduri sau coloane întregi remapează fiecare grup de panouri reprezentat, în ambele motoare, clasic și XLSX, printr-un singur remapper partajat. Zonele care supraviețuiesc își păstrează ordinea, iar zona activă își păstrează identitatea. Dacă zona activă este ștearsă, primul succesor care supraviețuiește devine activ, apoi ultimul predecesor supraviețuitor dacă nimic nu îi urmează. Dacă toate zonele sunt șterse, grupul se prăbușește într-o singură celulă la limita ștergerii, iar o celulă activă care nu se mai află în interiorul zonei alese se mută în colțul stânga-sus al zonei aceleia, astfel încât indexul și coordonata nu se contrazic niciodată. Limitele sunt deliberate. Grupurile clasice invalide sunt sărite de remapper, nu rescrise într-o selecție inventată, astfel încât byte-urile lor originale mai fac încă un drum dus-întors. Editarea unui panou înlocuiește doar înregistrările panoului aceluia și lasă celelalte identice byte cu byte. ODS nu primește deloc stare de selecție de panouri, pentru că ODF nu are nicio structură echivalentă de vizualizare a foii de lucru care să o poarte
Dacă aplicația dvs. scrie fișiere .xls pe care utilizatorii le deschid și în care trebuie să se orienteze, fie pentru a revisa celulele marcate, fie pentru a relua de unde au rămas, fie pentru a partaja un tablou de bord înghețat, API-ul de selecție și scroll conștient de panouri face parte din componenta de foi de calcul HotXLS pentru Delphi și funcționează la fel pentru XLS și XLSX