HotXLS انتخابهای کاربرگ و موقعیتهای اسکرول را بهازای هر پن، از طریق یک API پن-آگاه مشترک روی هر دو TXLSWorksheet و TXLSXWorksheet ذخیره میکند: SelectAreas و GetSelectedAreas و ScrollWindow و TryGetWindowScroll. برای فایلهای .xls کلاسیک، HotXLS رکوردهای Selection (0x001D) مربوط به BIFF8 را حداکثر با 1369 ناحیه در هر رکورد مینویسد، نامهای منطقی پن را به بایتهای پنی که فرمت تعریف کرده تبدیل میکند، و هر محور اسکرول را روی رکورد Window2 یا Pane همانجا که Excel انتظارش را دارد نگه میدارد
مشکل معمولاً در یک ابزار مغایرتگیری یا ممیزی خودش را نشان میدهد. ابزار یک export دفترکل را باز میکند، هر سلولی را که با سیستم مبدأ نمیخواند پیدا میکند، و ورکبوک را با همان سلولها که از قبل زیر یک ردیف هدر فریزشده انتخاب شدهاند ذخیره میکند تا بازبین تفاوتها را خودش پشت سر هم ببیند نه اینکه برایشان اسکرول کند. با چهل تفاوت خوب کار میکند. فایل آخر ماه 3000 تا دارد، و یک رکورد Selection تکی با 3000 ناحیه اصلاً نمیتواند وجود داشته باشد: بدنهاش به 18009 بایت نیاز دارد، بیش از دو برابر چیزی که یک رکورد BIFF8 میتواند حمل کند. موقعیت اسکرول هم تلهٔ مشابهی دارد. روی یک sheet با پنهای فریزشده، «جایی که کاربر نگاه میکرد» چهار پن است که دو موقعیت ردیفی و دو موقعیت ستونی را به اشتراک میگذارند، نه یک مختصات تکی
چرا یک انتخاب بزرگ به بیش از یک رکورد Selection نیاز دارد؟
یک انتخاب بزرگ به چند رکورد نیاز دارد چون بدنهٔ رکورد BIFF8 سقف 8224 بایت دارد و هر ناحیهٔ انتخابشده شش بایت ثابت هزینه دارد. [MS-XLS] §2.4.248 رکورد Selection را بهصورت یک بخش ثابت 9 بایتی (بایت پن، rwAct و colAct برای سلول فعال، irefAct برای ناحیهٔ فعال، و cref برای تعداد ناحیهها) بهعلاوهٔ cref ساختار RefU توصیف میکند که هر کدام دو ردیف 16 بیتی و دو ستون 8 بیتی حمل میکنند. بزرگترین تعدادی که جا میشود (8224 منهای 9) تقسیم بر 6 با گرد شدن به پایین است، یعنی 1369، که بدنهٔ 8223 بایتی میدهد، یک بایت زیر سقف. TXLSWorksheet.StoreSelectionGroup همین ثابت را بهعنوان MaxAreasPerRecord استفاده میکند و یک گروه بزرگتر را بهصورت رکوردهای Selection پشت سر هم برای همان پن مینویسد، هر بار 1369 ناحیه
جزئیاتی که گاز میگیرد irefAct است. هر chunk همان ردیف فعال و ستون فعال و اندیس ناحیهٔ فعال را تکرار میکند، و irefAct به دنبالهٔ تجمیعی همهٔ chunkها اندیس میدهد، نه به ناحیههای داخل رکوردی که حملش میکند. انتخابی که یک ناحیه از سقف جلوتر است این را عینی میکند: 1370 ناحیه که آخری فعال است دو رکورد میشود، اولی با cref برابر 1369 و دومی با cref برابر 1، و هر دو irefAct برابر 1369 حمل میکنند. این مقدار از تعداد ناحیهٔ خود رکورد دوم بزرگتر است. readerای که irefAct را با cref همان رکورد چک میکند یک فایل معتبر را رد میکند، و readerای که state خودش را با هر رکورد عوض میکند 1369 ناحیهٔ اول را گم میکند. reader مربوط به HotXLS رکوردهای پشتسرهمِ همپن را در یک گروه append میکند، از هر chunk میخواهد روی سلول فعال و اندیس توافق داشته باشند، و چک بازه را فقط در رکورد EOF کاربرگ اجرا میکند، وقتی دنبالهٔ کامل معلوم شده. پس overload پن-اولِ SelectAreas هیچ سقف 1369 ناحیهای ندارد. قبل از گرفتن قفل نوشتن کاربرگ هر ارجاع A1 و اندیس فعال را اعتبارسنجی میکند، و اگر چیزی بدشکل باشد False برمیگرداند و انتخاب قبلی دستنخورده میماند
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
Diffs: TXLSSelectedAreas;
I: Integer;
begin
Book := TXLSWorkbook.Create;
try
Sheet := Book.Sheets.Add;
Sheet.FreezePanes(1, 1); // ردیف هدر و ستون A سر جایشان میمانند
SetLength(Diffs, 3000);
for I := 0 to High(Diffs) do
Diffs[I] := Format('C%d', [I + 2]);
// فریز کردن انتخاب ذخیرهشده را reset میکند، پس بعد از فریز انتخاب کنید.
// 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;
هر رکورد Selection از کدام بایت پن استفاده میکند؟
یک رکورد Selection پن خودش را با کد عددیای که فرمت تعریف کرده مشخص میکند: 0 برای پایین-راست، 1 برای بالا-راست، 2 برای پایین-چپ، و 3 برای بالا-چپ. enumeration عمومی TXLSPanePosition به ترتیب خواندن تعریف شده، یعنی xlspTopLeft و xlspTopRight و xlspBottomLeft و xlspBottomRight، پس Ord(xlspTopLeft) برابر 0 است که همان پن پایین-راست در فایل است. cast کردن مستقیم enum به بایت پن هر انتخاب بالا-چپ را بدون هیچ خطایی روی پن پایین-راست مینویسد. هر نقطهٔ ورود پن-آگاه HotXLS این enum را بهجای آن از طریق یک case صریح تبدیل میکند، تا فراخوانها هرگز با کدهای عددی درگیر نشوند. وجود پن هم چک میشود: پن بالا-راست فقط با split عمودی وجود دارد، پایین-چپ فقط با split افقی، و پایین-راست فقط با هر دو. برای پنی که هندسهٔ فعلی split یا فریز ندارد، SelectAreas مقدار False برمیگرداند و GetSelectedAreas یک آرایهٔ خالی با ActiveAreaIndex برابر -1 برمیگرداند، بدون اینکه پن یا آبجکت انتخاب یا سلولی در ورکبوک بسازد
موقعیت اسکرول هر پن کجا زندگی میکند؟
موقعیت اسکرول هر پن بین دو رکورد پخش شده، چون چهار پن فقط دو موقعیت ردیفی و دو موقعیت ستونی را به اشتراک میگذارند. در یک ورکبوک کلاسیک، اولین ردیف دیدنی پنهای بالایی و اولین ستون دیدنی پنهای چپی Window2.rwTop و Window2.colLeft هستند، و ردیف پنهای پایینی و ستون پنهای راستی Pane.rwTop و Pane.colLeft. پس ScrollWindow(xlspTopRight, R, C) مقدار Window2.rwTop و Pane.colLeft را مینویسد، و ست کردن ستون پن بالا-راست پن پایین-راست را هم جابهجا میکند، درست مثل اینکه این دو در Excel یک اسکرولبار افقی مشترک دارند. متدهای عمومی از شمارهٔ ردیف و ستون 1-محور استفاده میکنند. پن ناموجود مقدار False برمیگرداند و هر دو خروجی کوئری را صفر میکند، و مختصات بیرون از بازه قبل از اینکه هر محوری عوض شود رد میشود. هیچجای اینها به نحوهٔ رنگ کردن گرید توسط viewer وابسته نیست. یک کنترل رندر TopRow و LeftCol خودش را نگه میدارد، همانطور که مقالهٔ رندر کردن ورکبوکها در یک گرید VCL سفارشی توضیح میدهد، و آنها state زمان اجرا هستند، نه چیزی که ذخیره میشود
XLSX همان داده را روی دو المان پخش میکند: sheetView/@topLeftCell (ECMA-376 Part 1، §18.3.1.87) برای پنجره در کل و فرزند pane/@topLeftCell (§18.3.1.66) برای سمت پایین-راست یک split. هر دو اتریبیوت میتوانند همزمان حاضر باشند. HotXLS اول اتریبیوت بیرونی را در فیلدهای سطح پنجره میخواند، اجازه میدهد فرزند pane فقط فیلدهای سطح پن را override کند، و هر دو را جداگانه برمیگرداند. فرو ریختن دو لایه در یکی دقیقاً همان راهی است که یک موقعیت اسکرول بالایی یا چپی موقع لود بیسروصدا گم میشود. کپیهای کاربرگ هر دو لایه را در هر دو موتور حمل میکنند. نقاط ورود قدیمیتر رفتار اصلیشان را دارند: propertyهای کلاسیک ScrollRow و ScrollColumn، و SetPaneScroll و GetPaneScroll مربوط به XLSX با اندیس صفر. خودِ هندسهٔ فریز و split با تنظیمات سطح sheet پیکربندی میشود که در محافظت از sheet، 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)، و موقع ذخیره بدنهٔ اصلی را بایتبهبایت برمیگرداند. قبل از اینکه یک رکورد به گروه پنش بپیوندد، reader به ترتیب آن را چک میکند. بایت پن باید 3 یا کمتر باشد. رکوردهای یک پن باید در stream پشت سر هم باشند. 9 بایت ثابت باید حاضر باشند. cref باید بین 1 و 1369 باشد، و بدنه باید دقیقاً 9 + cref × 6 بایت باشد. هر chunk گروه باید روی سلول فعال و irefAct توافق داشته باشد، بیت علامت irefAct نباید ست باشد، ستون فعال باید روی گرید باشد، و هیچ ناحیهای نباید مرزهای برعکس داشته باشد. مشکلات یک رکورد فیزیکی تکی بهازای هر رکورد یکبار گزارش میشوند. تناقضهایی که فقط بعد از تجمیع ظاهر میشوند، مثل irefActای که از تعداد کل ناحیهها جلوتر میرود یا سلول فعالی بیرون از ناحیهٔ اندیسی، بهازای هر گروه در EOF یکبار گزارش میشوند. یک گروه نامعتبر برای API تایپدار نامرئی میماند: GetSelectedAreas برای آن پن آرایهٔ خالی با اندیس -1 برمیگرداند، در حالی که هر پن دیگر سر کارش است
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;
انتخابها از insert کردن ردیف و ستون چطور جان سالم به در میبرند؟
انتخابها از ویرایشهای ساختاری جان سالم به در میبرند، چون insert یا delete کردن ردیف یا ستون کامل هر گروه پن نمایشدادهشده را در هر دو موتور کلاسیک و XLSX از طریق یک remapper مشترک بازنگاشت میکند. ناحیههای باقیمانده ترتیبشان را نگه میدارند و ناحیهٔ فعال هویتش را. اگر ناحیهٔ فعال حذف شود، اولین جانشینِ باقیمانده فعال میشود، و اگر چیزی دنبالش نباشد آخرین پیشینِ باقیمانده. اگر همهٔ ناحیهها حذف شوند، گروه به یک سلول در مرز حذف فرو میریزد، و سلول فعالی که دیگر داخل ناحیهٔ انتخابی نمیافتد به گوشهٔ بالا-چپ آن ناحیه میرود، تا اندیس و مختصات هرگز با هم تناقض نداشته باشند. سقفها عامدانهاند. گروههای کلاسیک نامعتبر توسط remapper رد میشوند بهجای اینکه به یک انتخاب از پیش ساختهشده بازنویسی شوند، پس بایتهای اصلیشان همچنان round trip میکنند. ویرایش یک پن فقط رکوردهای همان پن را عوض میکند و بقیه را byte-identical میگذارد. ODS اصلاً هیچ state انتخاب پن نمیگیرد، چون ODF هیچ ساختار view کاربرگ معادلی برای حملش ندارد
اگر اپلیکیشن شما فایلهای .xls مینویسد که کاربرها بازشان میکنند و باید درشان حرکت کنند، چه برای بازبینی سلولهای علامتخورده، چه برای ادامه از جایی که رها کردهاند، چه برای به اشتراک گذاشتن یک dashboard فریزشده، API انتخاب و اسکرول پن-آگاه بخشی از کامپوننت spreadsheet Delphi HotXLS است و برای XLS و XLSX به یک شکل کار میکند