HotXLS съхранява селекциите на листа и скрол позициите по pane чрез един pane-aware API както на TXLSWorksheet, така и на TXLSXWorksheet: SelectAreas, GetSelectedAreas, ScrollWindow и TryGetWindowScroll. За класически .xls файлове HotXLS записва BIFF8 Selection записи (0x001D) с най-много 1369 области всеки, превръща логическите pane имена в pane байтовете, които форматът дефинира, и пази всяка скрол ос на Window2 или Pane записа, където Excel го очаква
Проблемът обикновено изниква в инструмент за сверяване или одит. Инструментът отваря ledger export, намира всяка клетка, която не съвпада с source системата, и записва работната книга с тези клетки вече селектирани под замразен header ред, така че проверяващият каца директно върху разликите вместо да ги търси със скрол. При четиридесет разлики работи гладко. Файлът за месечното приключване има 3000, а един-единствен Selection record с 3000 области не може да съществува: тялото му би искало 18009 байта, над два пъти повече от това, което един BIFF8 запис може да понесе. Скрол позицията има подобен капан. На лист със замразени panes "къде е гледал потребителят" са четири pane-а, споделящи две редови и две колонови позиции, не една координата
Защо голямата селекция се нуждае от повече от един Selection запис?
Голямата селекция се нуждае от няколко записа, защото тялото на BIFF8 запис има таван от 8224 байта, а всяка селектирана област струва фиксирано шест байта. [MS-XLS] §2.4.248 разлага Selection записа на 9-байтова фиксирана част (pane байта, rwAct и colAct за активната клетка, irefAct за активната област и cref за броя области), следвана от cref на брой RefU структури, всяка с два 16-битови реда и две 8-битови колони. Най-големият брой, който се побира, е (8224 − 9) / 6 закръглено надолу, тоест 1369, което дава тяло от 8223 байта, един байт под лимита. TXLSWorksheet.StoreSelectionGroup ползва тази константа като MaxAreasPerRecord и записва по-голяма група като поредни Selection записи за същия pane, по 1369 области наведнъж
Деталят, който хапе, е irefAct. Всяко парче повтаря същите активен ред, активна колона и индекс на активна област, а irefAct индексира агрегираната последователност на всички парчета, не областите вътре в записа, който го носи. Селекция с една област над лимита прави това конкретно: 1370 области с последната активна стават два записа, първият с cref 1369 и вторият с cref 1, и двата носят irefAct 1369. Тази стойност е по-голяма от собствения брой области на втория запис. Reader, който проверява irefAct срещу cref във всеки запис, отхвърля валиден файл, а reader, който заменя състоянието си при всеки запис, губи първите 1369 области. HotXLS reader-ът долепя поредните записи от същия pane в една група, изисква всяко парче да се съгласява по активната клетка и индекс и пуска проверката за диапазон чак на EOF записа на листа, когато цялата последователност е известна. Pane-first SelectAreas overload-ът затова няма таван от 1369 области. Той валидира всяка A1 референция и активния индекс, преди да вземе worksheet write lock-а, и връща False с непокътната предишна селекция, ако нещо е недобре оформено
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
Diffs: TXLSSelectedAreas;
I: Integer;
begin
Book := TXLSWorkbook.Create;
try
Sheet := Book.Sheets.Add;
Sheet.FreezePanes(1, 1); // header редът и колона A остават на мястото
SetLength(Diffs, 3000);
for I := 0 to High(Diffs) do
Diffs[I] := Format('C%d', [I + 2]);
// Замразяването нулира съхранената селекция, затова селектирайте след замразяването.
// 3000 области се записват като три 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;
Кой pane байт ползва Selection записът?
Selection записът идентифицира своя pane с числовия код, който форматът дефинира: 0 за долния десен, 1 за горния десен, 2 за долния ляв и 3 за горния ляв. Публичното изброяване TXLSPanePosition е декларирано в четивен ред — xlspTopLeft, xlspTopRight, xlspBottomLeft, xlspBottomRight — така че Ord(xlspTopLeft) е 0, което е долният десен pane във файла. Кастването на enum-а направо в pane байта би записало всяка горна лява селекция върху долния десен pane без никаква грешка. Всеки pane-aware входен пункт на HotXLS конвертира enum-а чрез изричен case statement вместо това, така че извикващите никога не се занимават с числовите кодове. Съществуването на pane също се проверява: горният десен pane съществува само при вертикален split, долният ляв само при хоризонтален, а долният десен само при двата. За pane, който текущата split или freeze геометрия няма, SelectAreas връща False, а GetSelectedAreas връща празен масив с ActiveAreaIndex -1, без да създава pane, selection обект или клетка в работната книга
Къде живее скрол позицията на всеки pane?
Скрол позицията на всеки pane е разпределена в два записа, защото четири pane-а споделят само две редови и две колонови позиции. В класическа работна книга първият видим ред на горните pane-и и първата видима колона на левите pane-и са Window2.rwTop и Window2.colLeft, докато редът на долните pane-и и колоната на десните pane-и са Pane.rwTop и Pane.colLeft. ScrollWindow(xlspTopRight, R, C) затова записва Window2.rwTop и Pane.colLeft, а задаването на колоната на горния десен pane мести и долния десен pane, точно както двата споделят една хоризонтална scroll лента в Excel. Публичните методи ползват редове и колони номерирани от 1. Липсващ pane връща False и занулява и двата изхода на заявката, а координата извън диапазона се отхвърля, преди някоя ос да се е променила. Нищо тук не зависи от това как един viewer рисува мрежата. Rendering контролата държи собствени TopRow и LeftCol, както описва статията за рендериране на работни книги в персонализирана VCL мрежа, а това е runtime състояние, не това, което се записва
XLSX разпределя същите данни върху два елемента: sheetView/@topLeftCell (ECMA-376 Part 1, §18.3.1.87) за прозореца като цяло и дъщерният pane/@topLeftCell (§18.3.1.66) за долно-десната страна на split. И двата атрибута могат да присъстват едновременно. HotXLS прочита външния атрибут първо в полетата на ниво прозорец, оставя pane детето да предефинира само полетата на ниво pane и записва и двете обратно поотделно. Сливането на двата слоя в един е точно начинът, по който горна или лява скрол позиция тихо изчезва при зареждане. Копията на лист носят и двата слоя и в двата engine-а. По-старите входни точки пазят първоначалното си поведение: класическите свойства ScrollRow и ScrollColumn и zero-based XLSX SetPaneScroll и GetPaneScroll. Самата freeze и split геометрия се настройва със настройките на ниво лист, разгледани в статията за защита на листове, page setup и печатане
var
Row, Col: Integer;
begin
Sheet.FreezePanes(1, 1);
// Долен десен: долна редова ос (Pane.rwTop) и дясна колонова ос (Pane.colLeft)
Sheet.ScrollWindow(xlspBottomRight, 500, 3);
// Горен десен споделя дясната колонова ос, така че това мести и долния десен на колона 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]));
// Долният десен започва от ред 500, колона 6
end;
Какво става, когато Selection запис е повреден?
Когато Selection запис е повреден, HotXLS го пази като opaque байтове, докладва диагностичен код 1304 (xlsDiagnosticSelectionRecordInvalid) и записва оригиналното тяло обратно байт по байт при save. Преди запис да се присъедини към групата на своя pane, reader-ът го проверява по реда. Pane байтът трябва да е 3 или по-малко. Записите за един pane трябва да са съседни в потока. 9-те фиксирани байта трябва да присъстват. cref трябва да е между 1 и 1369, а тялото трябва да е точно 9 + cref × 6 байта дълго. Всяко парче в група трябва да се съгласява по активната клетка и irefAct, irefAct не бива да има вдигнат знаков бит, активната колона трябва да е върху мрежата, и нито една област не бива да има обърнати граници. Проблемите в един физически запис се докладват по веднъж на запис. Противоречията, които изникват едва след агрегация, като irefAct, сочещ над общия брой области, или активна клетка извън индексираната област, се докладват по веднъж на група на EOF. Невалидна група остава невидима за типизирания API: GetSelectedAreas връща празен масив с индекс -1 за този pane, докато всеки друг pane продължава да работи
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;
Как селекциите оцеляват при вмъкване на редове и колони?
Селекциите оцеляват при структурни редакции, защото вмъкването или изтриването на цели редове или колони пренасочва всяка представена pane група и в двата engine-а, Classic и XLSX, през един споделен remapper. Оцелелите области пазят реда си, а активната област пази идентичността си. Ако активната област е изтрита, активна става първият оцелял наследник, а ако няма такъв — последният оцелял предшественик. Ако всички области са изтрити, групата се свива до една клетка на границата на изтриването, а активна клетка, която вече не попада в избраната област, се премества в горния ѝ ляв ъгъл, така че индексът и координатата никога не си противоречат. Лимитите са нарочни. Невалидни classic групи remapper-ът прескача, вместо да ги пренапише в измислена селекция, така че оригиналните им байтове продължават да минават през round-trip. Редакция на един pane сменя само записите на този pane и оставя другите byte-identical. ODS изобщо не получава pane selection състояние, защото ODF няма еквивалентна worksheet view структура, която да го носи
Ако вашето приложение пише .xls файлове, които потребителите отварят и трябва да разцъкват — да преглеждат маркирани клетки, да продължават оттам, откъдето са спрели, или да споделят замразен dashboard — pane-aware selection и scroll API-ят е част от HotXLS Delphi spreadsheet компонента и работи по същия начин за XLS и XLSX