يخزن HotXLS تحديدات ورقة العمل ومواضع التمرير لكل لوح عبر API واحدة واعية بالألواح على كل من TXLSWorksheet وTXLSXWorksheet: وهي SelectAreas وGetSelectedAreas وScrollWindow وTryGetWindowScroll. وفي ملفات .xls الكلاسيكية يكتب HotXLS سجلات BIFF8 Selection (0x001D) لا تتجاوز 1369 منطقة لكل منها، ويحوّل أسماء الألواح المنطقية إلى بايتات الألواح التي يعرفها التنسيق، ويبقي كل محور تمرير على سجل Window2 أو Pane حيث يتوقعه Excel
تظهر المشكلة عادة في أداة مطابقة أو تدقيق. تفتح الأداة تصديرًا لدفتر أستاذ، وتجد كل خلية تخالف النظام المصدر، وتحفظ المصنف وتلك الخلايا محددة سلفًا تحت صف رأس مثبت، فيهبط المراجع على الفروق بدل أن يبحث عنها بالتمرير. مع أربعين فرقًا يعمل ذلك جيدًا. وملف نهاية الشهر فيه 3,000، وسجل Selection واحد يحمل 3,000 منطقة لا يمكن أن يوجد: جسمه يحتاج 18,009 بايتة، أي أكثر من ضعف ما يحمله سجل BIFF8 واحد. وموضع التمرير له فخ مشابه: في ورقة بألواح مثبتة، «أين كان المستخدم ينظر» أربعة ألواح تتقاسم موضعَي صفين وموضعَي عمودين، لا إحداثيًا واحدًا
لماذا يحتاج التحديد الكبير أكثر من سجل 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. تكرر كل دفعة الصف النشط والعمود النشط ومؤشر المنطقة النشط ذاتها، ويفهرس irefAct المتتالية المجمعة لكل الدفعات، لا المناطق داخل السجل الحامل له. تحديد بمنطقة واحدة فوق السقف يجعل الأمر ملموسًا: 1370 منطقة وآخرها نشط يصيران سجلين، الأول بـ cref مساوٍ 1369 والثاني بـ cref مساوٍ 1، وكلاهما يحمل irefAct مساويًا 1369. تلك القيمة أكبر من عدد مناطق السجل الثاني نفسه. قارئ يفحص irefAct مقابل cref في كل سجل يرفض ملفًا صحيحًا، وقارئ يستبدل حالته عند كل سجل يسقط أول 1369 منطقة. قارئ HotXLS يلحق السجلات المتتالية للوح نفسه في مجموعة واحدة، ويشترط أن تتفق كل دفعة على الخلية النشطة والمؤشر، ويجري فحص المدى عند سجل EOF لورقة العمل فقط، بعد معرفة المتتالية كاملة. لذا فإن التحميل الزائد 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]);
// التثبيت يصفر التحديد المخزن، فحدّد بعد التثبيت.
// تحفظ 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 للأعلى الأيسر. أما التعداد العام TXLSPanePosition فمعلن بترتيب القراءة: xlspTopLeft ثم xlspTopRight ثم xlspBottomLeft ثم xlspBottomRight، فيساوي Ord(xlspTopLeft) الصفر، وهو لوح الأسفل الأيمن في الملف. صبّ التعداد مباشرة في بايت اللوح سيكتب كل تحديد في الأعلى الأيسر على لوح الأسفل الأيمن بلا أي خطأ. وكل نقطة دخول في HotXLS واعية بالألواح تحوّل التعداد عبر عبارة case صريحة بدل ذلك، فلا يتعامل المستدعون مع الرموز الرقمية إطلاقًا. ويفحص وجود اللوح كذلك: لوح الأعلى الأيمن لا يوجد إلا مع تقسيم رأسي، والأسفل الأيسر إلا مع تقسيم أفقي، والأسفل الأيمن إلا مع الاثنين. وللوح لا تمتلكه هندسة التقسيم أو التثبيت الحالية، يعيد SelectAreas قيمة False، ويعيد GetSelectedAreas مصفوفة فارغة وActiveAreaIndex مضبوطًا إلى -1، دون إنشاء لوح أو كائن تحديد أو خلية في المصنف
أين يسكن موضع تمرير كل لوح؟
موضع التمرير لكل لوح مقسم على سجلين، لأن الألواح الأربعة تتقاسم موضعَي صفين وموضعَي عمودين فقط. في المصنف الكلاسيكي، أول صف مرئي للألواح العلوية وأول عمود مرئي للألواح اليسرى هما Window2.rwTop وWindow2.colLeft، بينما صف الألواح السفلية وعمود الألواح اليمنى هما Pane.rwTop وPane.colLeft. لذا يكتب ScrollWindow(xlspTopRight, R, C) في Window2.rwTop وPane.colLeft، وضبط عمود اللوح الأعلى الأيمن يحرك لوح الأسفل الأيمن أيضًا، تمامًا كما يتقاسم الاثنان شريط تمرير أفقي واحد في Excel. الطرق العامة تستخدم أرقام صفوف وأعمدة تبدأ من 1. اللوح الغائب يعيد False ويضبط مخرجي الاستعلام كليهما إلى الصفر، والإحداثية خارج المدى ترفض قبل أن يتغير أي محور. ولا شيء هنا يعتمد على كيفية رسم العارض للشبكة. عنصر تحكم الرسم يحتفظ بـ TopRow وLeftCol خاصتيه، كما يشرح مقال رسم المصنفات في شبكة VCL مخصصة، وتلك حالة زمن تشغيل، لا ما يُحفظ
ينشر XLSX البيانات نفسها فوق عنصرين: sheetView/@topLeftCell (ECMA-376 Part 1، §18.3.1.87) للنافذة ككل، وابنها pane/@topLeftCell (§18.3.1.66) للجهة السفلية اليمنى من التقسيم. ويمكن لحالتي الخاصتين أن تحضرا معًا. يقرأ HotXLS الخاصية الخارجية أولًا في حقول مستوى النافذة، ويدع ابن pane يتجاوز حقول مستوى اللوح فقط، ويكتبهما معًا منفصلتين. دمج الطبقتين في واحدة هو بالضبط كيف يتلاشى بصمت موضع تمرير علوي أو أيسر عند التحميل. نسخ أوراق العمل تحمل الطبقتين في كلا المحركين. ونقاط الدخول الأقدم تحافظ على سلوكها الأصلي: خاصيتا ScrollRow وScrollColumn الكلاسيكيتان، وSetPaneScroll وGetPaneScroll في XLSX ذواتا الترقيم من الصفر. أما هندسة التثبيت والتقسيم ذاتها فتضبط بإعدادات مستوى الورقة المغطاة في حماية الورقة وإعداد الصفحة والطباعة
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 بايتات معتمة، ويبلغ عن رمز تشخيص 1304 (xlsDiagnosticSelectionRecordInvalid)، ويعيد كتابة الجسم الأصلي بايتًا بايتًا عند الحفظ. وقبل أن ينضم سجل إلى مجموعة لوحه، يفحصه القارئ بالترتيب: يجب أن يكون بايت اللوح 3 أو أقل. يجب أن تكون سجلات اللوح الواحد متجاورة في التدفق. يجب أن تحضر البايتات الثابتة التسع. يجب أن يقع cref بين 1 و1369، وأن يكون طول الجسم 9 + cref × 6 بايتة بالضبط. ويجب أن تتفق كل دفعة في المجموعة على الخلية النشطة و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;
كيف تنجو التحديدات من إدراج الصفوف والأعمدة؟
تنجو التحديدات من التعديلات البنيوية لأن إدراج صفوف أو أعمدة كاملة أو حذفها يعيد تعيين كل مجموعة ألواح ممثلة في كلا المحركين الكلاسيكي وXLSX عبر معيد تعيين مشترك واحد. المناطق الناجية تحافظ على ترتيبها، والمنطقة النشطة تحافظ على هويتها. وإن حذفت المنطقة النشطة، صار الناجي الأول بعدها نشطًا، ثم آخر سابق ناجٍ إن لم يكن بعده شيء. وإن حذفت كل المناطق، انهارت المجموعة إلى خلية واحدة عند حد الحذف، والخلية النشطة التي لم تعد داخل المنطقة المختارة تنتقل إلى ركنها العلوي الأيسر، فلا يتناقض المؤشر مع الإحداثية أبدًا. الحدود مقصودة. المجموعات الكلاسيكية غير الصحيحة يتخطاها معيد التعيين بدل إعادة كتابتها تحديدًا مختلقًا، فتبقى بايتاتها الأصلية تمر ذهابًا وإيابًا. تحرير لوح واحد يستبدل سجلات ذلك اللوح فقط ويترك البقية متطابقة بايتيًا. وODS لا تحصل على حالة تحديد ألواح إطلاقًا، لأن ODF لا تملك بنية عرض ورقة عمل مكافئة تحملها
إن كان تطبيقك يكتب ملفات .xls يفتحها المستخدمون ويحتاجون التنقل فيها، سواء لمراجعة خلايا معلمة أو استكمال ما توقفوا عنده أو مشاركة لوحة معلومات مثبتة، فإن API التحديد والتمرير الواعية بالألواح جزء من مكوّن HotXLS للجداول لدلفي، وتعمل بالطريقة نفسها مع XLS وXLSX