A HotXLS a munkalap-kijelöléseket és a görgetési pozíciókat pane-enként tárolja egyetlen, panetudatos API-n keresztül, a TXLSWorksheet és a TXLSXWorksheet oldalán egyaránt: SelectAreas, GetSelectedAreas, ScrollWindow és TryGetWindowScroll. Klasszikus .xls fájloknál a HotXLS legfeljebb 1369 területet hordozó BIFF8 Selection rekordokat (0x001D) ír, a logikai pane-neveket a formátum által definiált pane-bájtokra képezi le, és minden görgetési tengelyt ott tart a Window2 vagy Pane rekordon, ahol az Excel várja
A probléma jellemzően egy egyeztető vagy auditáló eszközben bukik fel. Az eszköz megnyit egy főkönyv-exportot, megkeres minden olyan cellát, amely ellentmond a forrásrendszernek, és a munkafüzetet úgy menti el, hogy ezek a cellák már ki vannak jelölve egy rögzített fejlécsor alatt, így az ellenőr egyből a különbségeken találja magát, nem pedig görgetés utánuk. Negyven különbségnél ez szépen működik. A hóvégi fájlban 3000 van, és egyetlen Selection rekord sem létezhet 3000 területtel: a törzse 18 009 bájt lenne, több mint kétszerese annak, amennyit egy BIFF8 rekord elbír. A görgetési pozíciónál is ott a csapda. Rögzített pane-ekkel rendelkező munkalapon az „ott, ahol a felhasználó nézett" négy pane, amely két sorpozíciót és két oszloppozíciót oszt meg, nem pedig egyetlen koordináta
Miért van egy nagy kijelölésnek több Selection rekordra szüksége?
A nagy kijelölésnek több rekordra van szüksége, mert egy BIFF8 rekord törzse 8224 bájtnál tetőzik, és minden kijelölt terület fix hat bájtot emészt fel. A [MS-XLS] §2.4.248 egy 9 bájtos rögzített résszel írja le a Selection rekordot (a pane-bájt, a rwAct és a colAct az aktív cellához, az irefAct az aktív területhez, a cref pedig a területszámhoz), amelyet cref darab RefU struktúra követ, mindegyik két 16 bites sort és két 8 bites oszlopot hordozva. A beférő legnagyobb darabszám a lefelé kerekített (8224 − 9) / 6, ami 1369, és ez 8223 bájtos törzset ad, egy bájttal a határ alatt. A TXLSWorksheet.StoreSelectionGroup ezt a konstansot használja MaxAreasPerRecord-ként, és egy nagyobb csoportot ugyanannak a pane-nek szóló, egymást követő Selection rekordokként ír ki, egyszerre 1369 területtel
Az a részlet, amely beleharap, az irefAct. Minden darab ugyanazt az aktív sort, aktív oszlopot és aktív területindexet ismétli, az irefAct pedig valamennyi darab összefésült sorozatát indexeli, nem az éppen hordozó rekordjában lévő területeket. A határ fölé egy területtel érkező kijelölés konkréttá teszi ezt: 1370 terület, az utolsót aktívvá téve, két rekordot eredményez, az első cref 1369-tyal, a második cref 1-gyel, és mindkettő irefAct 1369-et hordoz. Ez az érték nagyobb, mint a második rekord saját területdarabszáma. Az az olvasó, amely rekordonként veti össze az irefAct-et a cref-fel, elutasít egy érvényes fájlt, az pedig, amely minden rekordnál lecseréli az állapotát, elejti az első 1369 területet. A HotXLS olvasója az egymást követő, azonos pane-es rekordokat egyetlen csoportba fűzi, megköveteli, hogy minden darab egyetértsen az aktív cellában és az indexben, a tartományellenőrzést pedig csak a munkalap EOF rekordjánál futtatja, ha a teljes sorozat már ismert. A pane-első SelectAreas túlterhelésnek ezért nincs 1369 területes plafonja. Minden A1-hivatkozást és az aktív indexet érvényesíti, még mielőtt megszerezné a munkalap írási zárolását, és ha bármi hibásan formázott, False-szal tér vissza, a korábbi kijelölés változatlanul
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
Diffs: TXLSSelectedAreas;
I: Integer;
begin
Book := TXLSWorkbook.Create;
try
Sheet := Book.Sheets.Add;
Sheet.FreezePanes(1, 1); // a fejlécsor és az A oszlop a helyén marad
SetLength(Diffs, 3000);
for I := 0 to High(Diffs) do
Diffs[I] := Format('C%d', [I + 2]);
// A rögzítés alaphelyzetbe állítja a tárolt kijelölést, ezért utána jelöljön.
// 3000 terület három Selection rekordként mentődik: 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;
Melyik pane-bájtot használ egy Selection rekord?
A Selection rekord a formátum által definiált numerikus kóddal azonosítja a pane-jét: 0 a jobb alsó, 1 a jobb felső, 2 a bal alsó, 3 pedig a bal felső számára. A nyilvános TXLSPanePosition enumeráció olvasási sorrendben van deklarálva, xlspTopLeft, xlspTopRight, xlspBottomLeft, xlspBottomRight, így az Ord(xlspTopLeft) 0, ami a fájlban a jobb alsó pane. Az enumeráció panebájtba öntése minden bal felső kijelölést hibajelzés nélkül a jobb alsó pane-re írna. Minden panetudatos HotXLS belépési pont ezért explicit case utasításon keresztül konvertálja az enumerációt, így a hívó soha nem találkozik a numerikus kódokkal. A pane létezése is ellenőrzés alá esik: a jobb felső pane csak függőleges osztással létezik, a bal alsó csak vízszintessel, a jobb alsó pedig mindkettővel. Olyan pane-nél, amelyet az aktuális osztási vagy rögzítési geometria nem tartalmaz, a SelectAreas False-szal tér vissza, a GetSelectedAreas pedig üres tömböt ad, az ActiveAreaIndex -1-en, pane, kijelölési objektum vagy cella létrehozása nélkül a munkafüzetben
Hol lakik minden pane görgetési pozíciója?
Minden pane görgetési pozíciója két rekordra oszlik, mert négy pane mindössze két sorpozíciót és két oszloppozíciót oszt meg. Klasszikus munkafüzetben a felső pane-ek első látható sora és a bal oldali pane-ek első látható oszlopa a Window2.rwTop és a Window2.colLeft, az alsó pane-ek sora és a jobb oldali pane-ek oszlopa pedig a Pane.rwTop és a Pane.colLeft. A ScrollWindow(xlspTopRight, R, C) ezért a Window2.rwTop-ot és a Pane.colLeft-et írja, és a jobb felső pane oszlopának beállítása a jobb alsó pane-t is magával mozgatja, éppúgy, ahogy a kettő az Excelben közös vízszintes görgetősávot használ. A nyilvános metódusok 1-alapú sorszámot és oszlopszámot használnak. Hiányzó pane False-ot ad, és mindkét lekérdezési kimenetet nullára állítja, a tartományon kívüli koordinátát pedig még bármelyik tengely változása előtt elutasítja a hívás. Semmi sem függ attól, hogyan fest egy megjelenítő a rácsot. Egy renderelő vezérlő a saját TopRow-ját és LeftCol-ját tartja, ahogy az a munkafüzetek egyéni VCL rácsban történő megjelenítéséről szóló cikk is írja, ezek pedig futásidejű állapotok, nem az, ami mentésre kerül
Az XLSX ugyanezeket az adatokat két elemre osztja szét: sheetView/@topLeftCell (ECMA-376 Part 1, §18.3.1.87) az egész ablakhoz, és a gyerek pane/@topLeftCell (§18.3.1.66) egy osztás jobb alsó oldalához. Mindkét attribútum ott lehet egyszerre. A HotXLS először a külső attribútumot olvassa be az ablakszintű mezőkbe, a pane gyerek csak a pane-szintű mezőket bírálhatja felül, és mindkettőt külön-külön írja vissza. A két réteg egybeolvasztása pontosan az, ahogy egy felső vagy bal görgetési pozíció betöltéskor csendben eltűnik. A munkalapmásolatok mindkét réteget hordozzák mindkét motorban. A régebbi belépési pontok meg tartják az eredeti viselkedésüket: a klasszikus ScrollRow és ScrollColumn tulajdonságok, valamint a nullaalapú XLSX SetPaneScroll és GetPaneScroll. Maga a rögzítési és osztási geometria a munkalapszintű beállításokkal konfigurálható, amelyeket a munkalapvédelemről, lapbeállításról és nyomtatásról szóló cikk fed le
var
Row, Col: Integer;
begin
Sheet.FreezePanes(1, 1);
// Jobb alsó: alsó sortengely (Pane.rwTop) és jobb oszloptengely (Pane.colLeft)
Sheet.ScrollWindow(xlspBottomRight, 500, 3);
// A jobb felső megosztja a jobb oszloptengelyt, ezért ez a jobb alsót is a 6. oszlopra viszi
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]));
// A jobb alsó az 500. sorban és a 6. oszlopban indul
end;
Mi történik, ha egy Selection rekord sérült?
Ha egy Selection rekord sérült, a HotXLS átlátszatlan bájtokként tartja meg, a 1304-es diagnosztikai kódot (xlsDiagnosticSelectionRecordInvalid) jelenti, és mentéskor bájtról bájtra visszaírja az eredeti törzset. Mielőtt egy rekord csatlakozna a pane-je csoportjához, az olvasó sorban ellenőrzi. A pane-bájtnak 3 vagy annál kisebbnek kell lennie. Egy pane rekordjainak folytonosnak kell lenniük a streamben. A 9 rögzített bájtnak jelen kell lennie. A cref-nek 1 és 1369 közé kell esnie, a törzsnek pedig pontosan 9 + cref × 6 bájt hosszúnak kell lennie. Minden darabnak egyet kell érteni csoporton belül az aktív cellában és az irefAct-ben, az irefAct előjelbitje nem állhat, az aktív oszlopnak a rácson kell lennie, és egyetlen terület sem lehet fordított határokkal. Az egyetlen fizikai rekordon belüli problémák rekordonként egyszer jelentenek. Azok az ellentmondások, amelyek csak az összesítés után bukkannak fel, például a teljes területdarabszámot túllépő irefAct vagy az indexelt területen kívüli aktív cella, csoportonként egyszer jelentenek az EOF-nál. Egy érvénytelen csoport láthatatlan marad a típusos API előtt: a GetSelectedAreas üres tömböt ad, -1 indexszel arra a pane-re, miközben minden más pane tovább dolgozik
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;
Hogyan élik túl a kijelölések a sor- és oszlopbeszúrásokat?
A kijelölések túlélik a szerkezeti szerkesztéseket, mert teljes sorok vagy oszlopok beszúrása és törlése mindkét motorban, a klasszikusban és az XLSX-ben is, egyetlen közös áthelyezőn keresztül képezi át minden reprezentált pane-csoportot. A túlélő területek megtartják sorrendjüket, az aktív terület pedig az identitását. Ha az aktív terület törlődik, az első túlélő utód lesz aktív, ha pedig utód nincs, az utolsó túlélő előd. Ha minden terület törlődik, a csoport a törlés határán egyetlen cellára összezsugorodik, és az aktív cella, amely nem esik többé a választott területbe, annak a területnek a bal felső sarkába ugrik, így az index és a koordináta soha nem mondanak ellent egymásnak. A határok szándékosak. Az érvénytelen klasszikus csoportokat az áthelyező kihagyja, nem írja át kitalált kijelöléssé, így az eredeti bájtaik továbbra is körbejárnak. Egy pane szerkesztése csak az adott pane rekordjait cseréli, a többit bájtonként azonosan hagyja. Az ODS pane-kijelölési állapotot egyáltalán nem kap, mert az ODF-nek nincs megfelelő munkalapnézet-struktúrája, amely hordozhatná
Ha az Ön alkalmazása olyan .xls fájlokat ír, amelyeket a felhasználók megnyitnak, és amelyekben navigálniuk kell, legyen szó megjelölt cellák átnézéséről, arról, hogy ott folytassák, ahol abbahagyták, vagy egy rögzített dashboard megosztásáról, a panetudatos kijelölési és görgetési API a HotXLS Delphi táblázatkezelő komponens része, és XLS-nél és XLSX-nél egyaránt ugyanúgy működik