مقال تقني

قوائم dropdown والتحقق من الإدخال في Delphi مع HotXLS

يقرأ HotXLS قوائم dropdown للتحقق من صحة البيانات في Excel مباشرة من كود Delphi. تعيد TryGetCellDropdownItems السلاسل المرشحة لخلية، وتكتب SelectCellDropdownItem إحداها بالفهرس أو بالنص، ويسجل ValidateCellInput هل ستتجاوز قيمة مكتوبة قاعدة الخلية قبل تخزين أي شيء. والتوابع نفسها موجودة على TXLSWorksheet للـ Classic XLS وعلى TXLSXWorksheet للـ XLSX، وكلاهما يأخذ أرقام صفوف وأعمدة بترقيم من 1

المحفز المعتاد للاحتياج لهذا قالب لم تصممه: شخص من الجهة التجارية يشحن مصنفًا بعمود Status مقفولًا على Draft وReview وFinal، وعمود Channel يتغذى من نطاق في ورقة مخفية، وعمود كمية لا يقبل إلا من 1 إلى 100. ومهمة الاستيراد أو نموذج ويب أو المالئ الدفعي لديك يصبح مضطرًا لاحترام تلك القواعد، والقواعد تسكن المصنف لا كودك. وترميز القيم المسموحة صلبًا بجوار القالب هو الطريقة التي تنشعب بها الاثنتان أول مرة يحرر أحد القائمة في Excel

لماذا تتجاهل كتابات الكود التحقق من صحة البيانات في Excel؟

التحقق من صحة البيانات بيانات وصفية ملحقة بنطاق، وفرضها شأن أي محرر يكتب فيه المستخدم. في XLSX القاعدة عنصر dataValidation (الجزء 1 من ECMA-376، §18.3.1.32) يحمل النوع والمعامل وformula1 وformula2 والطلب وتنبيه الخطأ. وفي ملف Classic XLS تجلس المعلومة نفسها في سجل DV الموصوف في [MS-XLS]. ولا ممن الصيغتين يميز الخلية نفسها، فلا شيء يمنع برنامجًا من تخزين قيمة تكسر القاعدة. ويتصرف Excel بالطريقة نفسها: يفحص المدخلات المكتوبة، والقيمة خارج المدى التي وصلت بوسائل أخرى لا تظهر إلا حين يشغل أحد Circle Invalid Data

يتبع HotXLS ذلك النموذج بقصد: إسناد مثل Sheet.Cells[3, 2].Value := 250 يخزن 250 تحت قاعدة من 1 إلى 100 دون شكوى، تمامًا كما تفعل عرضة DataValidation المرفقة لإثبات النقطة. والفحص اختياري الانضمام، وبقية هذه المقالة عن الانضمام في الموضع الصحيح

قراءة عناصر dropdown خلف خلية

تعيد TryGetCellDropdownItems(ARow, ACol, AItems, AShowDropDown) ‏True فقط حين تغطي القاعدة الخلية من نوع قائمة، وتملا AItems بـ TArray<WideString> من المرشحين. ويحل HotXLS الطرق الأربعة التي تكتب بها مصادر القوائم عمليًا، فلا يحلل المستدعي formula1 يدويًا أبدًا:

  • قائمة لاصقة مقتبسة مثل "Draft,Review,Final" مقسمة على الفواصل
  • قائمة لاصقة تفصل عناصرها محارف #0، وهي صيغة توجد في قواعد Classic XLS
  • نطاق في الورقة نفسها أو في ورقة أخرى، مثل =$G$2:$G$10 أو =Lists!$A$2:$A$7، يقيَّم إلى قيم خلاياه الحالية
  • اسم معرَّف مثل =ProductList يشير إلى أحد تلك النطاقات

الخلايا المدمجة يعالجها أيضًا: اطلب أي خلية داخل دمج فيبحث HotXLS القاعدة عند مرساة الدمج، وهي موضع احتفاظ Excel بالقاعدة والقيمة معًا

حل dropdown في HotXLS على Delphi: ‏TryGetCellDropdownItems يخط خلية أو مرساة دمج إلى قاعدة قائمة تتغذى من قائمة لاصقة أو نطاق أو اسم معرَّف
يمرر المستدعي أرقام صف وعمود بترقيم من 1 ويحل HotXLS أشكال مصادر القوائم الأربعة كلها، فلا يحلل أي كود formula1 يدويًا

علم AShowDropDown يستحق ملاحظة، لأن صيغ الملفات تخزنه بالعكس: في XLSX فإن showDropDown="1" يخفي فعليًا السهم داخل الخلية، وفي BIFF8 البت المكافئ علم "كبت combo". يقلب HotXLS الاثنين عند القراءة، فـ AShowDropDown = True تعني ما تقول: كان Excel سيعرض السهم. وقاعدة قائمة والسهم مخفي ما تزال تقيد الإدخال، ولهذا تعيد TryGetCellDropdownItems العناصر في الحالتين

انقلاب علم showDropDown في Delphi مع HotXLS: يخزن XLSX قيمة 1 ويخزن BIFF8 بت كبت combo لإخفاء السهم، ويعاد قلبه عند القراءة
كلا الصيغتين تخزنان علم القائمة المنسدلة بالعكس، فـ AShowDropDown = True تعني أن كان Excel سيعرض سهم الخلية فعلًا
uses
  SysUtils, lxHandleX;

procedure DumpChannelList(const FileName: WideString);
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Items: TArray<WideString>;
  ShowArrow: Boolean;
  I: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open(FileName) <> 1 then
      raise Exception.Create('Cannot open ' + FileName);
    Sheet := Book.Sheets[1];
    // ‏F2: صف 2 عمود 6، كلاهما بترقيم من 1
    if Sheet.TryGetCellDropdownItems(2, 6, Items, ShowArrow) then
    begin
      Writeln(Length(Items), ' items, arrow visible: ', BoolToStr(ShowArrow, True));
      for I := 0 to High(Items) do
        Writeln('  [', I, '] ', Items[I]);
    end
    else
      Writeln('F2 has no list validation');
  finally
    Book.Free;
  end;
end;

إن كنت تحتاج السلاسل فقط، فتعيد GetCellDropdownItems(ARow, ACol) المصفوفة مباشرة وتعطي مصفوفة فارغة حين لا تنطبق قائمة. ويعيد TryGetCellValidationPrompt عنوان نص رسالة الإدخال ونصه، وهو مفيد حين تريد عرض تلميح مؤلف القالب نفسه بجوار حقل نموذج ويب

اختيار عنصر بالفهرس أو بالنص

تكتب SelectCellDropdownItem إدخال قائمة في الخلية وتعيد False حين لا قاعدة قائمة للخلية أو الإدخال المطلوب غير موجود. والنسخة الصحيحة تأخذ فهرسًا بترقيم من 0 داخل المصفوفة التي أعادت TryGetCellDropdownItems. وتبحث النسخة النصية عن تطابق تام أولًا ثم تعود إلى تطابق غير حساس لحالة الأحرف، فـ 'review' ما زالت تختار Review

وكلا النسختين تكتب عبر WriteAutoValue لا عبر إسناد سلسلة خام. وهذا مهم للقوائم المدعومة بنطاق من الأعداد: العناصر تعود سلاسل مثل '3'، لكن القيمة المخزنة هي العدد 3، وهو ما يخزنه Excel حين يختار المستخدم من السهم وما تتوقعه أي صيغة تشير إلى الخلية. ويفحص عرض DataValidation هذا بالضبط بعد حفظ وإعادة فتح في XLS وXLSX وXLSB

// الاستدعاءات نفسها على TXLSWorksheet ‏(XLS) وTXLSXWorksheet ‏(XLSX)
if not Sheet.SelectCellDropdownItem(2, 1, 'Review') then
  Writeln('A2: "Review" is not in the list');

if not Sheet.SelectCellDropdownItem(2, 6, 3) then  // العنصر الرابع، بترقيم من 0
  Writeln('F2: index 3 is out of range');

كيف تتحقق من إدخال مكتوب قبل كتابته؟

تختبر ValidateCellInput(ARow, ACol, AText) سلسلة مرشحة مقابل القاعدة المغطية للخلية ولا تعدل الخلية أبدًا. وتعيد True حين لا تنطبق قاعدة أو المرشح يجتاز. وتمر السلسلة عبر التحويل نفسه من نص إلى قيمة الذي تستخدمه WriteAutoValue، فيرى الفحص والكتابة النهائية القيمة نفسها. وعلى جانب XLSX يكون ذلك التحويل XlsxAutoTextValue: يتعرف على TRUE/FALSE وliteralات الأخطاء وتواريخ ISO وتواريخ m/d/yyyy وd.m.yyyy والأعداد بالصيغة الثابتة، ويحفظ 0123 نصًا كما يفعل Excel. ومحرك Classic XLS له محوِّله الخاص الذي يحاكي WriteAutoValue الخاصة به

رفيقان يكملان المجموعة: يفحص ValidateCellValue(ARow, ACol) القيمة المخزنة أصلًا في الخلية ويعامل الخلايا الفارغة صالحة. ويعيد TryGetCellValidationError عنوان تنبيه خطأ القاعدة ونصه ونمطه، ويعيد False حين لا تعرض القاعدة تنبيه خطأ أصلًا. ونوع النمط يختلف بحسب المحرك، فطابقه مع صنف ورقة العمل: ‏TXLSDvErrorStyle من lxDataCheck لـ TXLSWorksheet، وTXLSXDvErrorStyle لـ TXLSXWorksheet

سلوك واحد عليك إعادة إنتاجه بنفسك: لا يتحقق Excel من إدخال فارغ أو أي شيء يبدأ بـ =. ويطبق HotXLS ذلك التخطي في مسار تحرير الـ viewer لا داخل ValidateCellInput، فيجب على بوابة على جانب الخادم أن تفعل الشيء نفسه قبل استدعائها:

uses
  lxDataCheck, lxHandle;

// تعيد True حين يجوز كتابة AText، محاكية أنماط تنبيه Excel
function AcceptTypedValue(Sheet: TXLSWorksheet; ARow, ACol: Integer;
  const AText: WideString; out AMessage: WideString): Boolean;
var
  Title, Text: WideString;
  Style: TXLSDvErrorStyle;
begin
  AMessage := '';
  Result := True;
  // ‏Excel يتخطى التحقق للخلايا الممحوة والصيغ
  if (AText = '') or (AText[1] = '=') then
    Exit;
  // لا تنبيه خطأ مضبوطًا: ‏Excel يمرر أي شيء
  if not Sheet.TryGetCellValidationError(ARow, ACol, Title, Text, Style) then
    Exit;
  if Sheet.ValidateCellInput(ARow, ACol, AText) then
    Exit;
  AMessage := Title + ': ' + Text;
  // ‏Stop يرفض؛ وWarning وInformation تتركان القرار للمستدعي
  Result := Style <> xlsDvErrStop;
end;

// المستدعي (Msg: WideString): تحقق، ثم خزّن بالتحويل نفسه
if AcceptTypedValue(Sheet, 2, 2, '42', Msg) then
  Sheet.WriteAutoValue(2, 2, '42');

إقران ValidateCellInput مع WriteAutoValue هو مغزى المثال: تحقق بتحويل واخزن بآخر، وعاجلًا أو آجلًا ستجتاز قيمة الفحص كعدد وتهبط في الخلية نصًا

خط التحقق-ثم-الكتابة في Delphi مع HotXLS: ‏ValidateCellInput يحول النص تمامًا مثل WriteAutoValue، ونمط التنبيه يقرر بين الحجب واختيار المستدعي
تحقق بالتحويل نفسه الذي تكتب به، وإلا فإن قيمة قد تجتاز الفحص عددًا وتهبط في الخلية نصًا

عناصر القوائم المقتبسة ومصيدة التهريب في Classic XLS

عناصر قوائم لاصقة تحوي اقتباسات مزدوجة طريقة سهلة لفقدان قاعدة بصمت: القائمة اللاصقة تخزن كسلسلة صيغة، وسلسلة الصيغة تهرب اقتباسًا مضمَّنًا " بمضاعفته. لف العناصر اقتباسًا دون مضاعفة الداخلي، وقائمة مثل He said "hi",No تنتج صيغة لا تترجم. ولا استثناء: القاعدة ببساطة لا تنشأ، وتذهب القائمة المنسدلة معها. ويضاعف TXLSWorksheet.AddListValidation ونظيره في XLSX كلاهما الاقتباسات المضمَّنة، فتنجو تلك العناصر سليمة

لدى TXLSWorksheet.AddListValidation أيضًا نسخة بـ array of WideString مطابقة للنسخة على TXLSXWorksheet. وهي مريحة، لكن اقرأ ما تفعله: تلحق العناصر بفواصل وتسلم النتيجة للنسخة النصية. فعنصر يحوي بذاته فاصلة يصير عنصرين، وعلى المحرك الكلاسيكي تؤخذ سلسلة مصدر تحوي ! مرجعًا مؤهَّلًا بورقة لا قائمة لاصقة. وللعناوين من ذلك القبيل، ضع القيم في نطاق خلايا ووجه القاعدة إليه

var
  Book: IXLSWorkbook;
  Sheet: TXLSWorksheet;
begin
  Book := TXLSWorkbook.Create;
  Sheet := Book.Sheets.Add;
  Sheet.AddListValidation('H2:H11', ['He said "hi"', 'Plain option']);
  // أكد وجود القاعدة قبل الاعتماد عليها
  if Sheet.FindDataValidationAt(2, 8) = nil then
    raise Exception.Create('List rule for H2:H11 was not created');
  // ...
end;

بعد حفظ وإعادة فتح، تعيد TryGetCellDropdownItems على H2 قيمة He said "hi" باقتباساتها سليمة، في خرج XLS وXLSX وXLSB وODS على السواء

ترك TXLSWorkbookViewer يفرض القواعد

حين يحرر المستخدمون الخلايا على الشاشة، يطبق TXLSWorkbookViewer الفحوص نفسها عنك: مع Editable على True وترك EnforceDataValidation على افتراضه True، يفحص تحرير مكتوب يكسر قاعدة ذات تنبيه خطأ قبل كتابته: ‏Stop يرفض، وWarning يسأل، وInformation يخبر. ويرسم الـ viewer أيضًا سهم القائمة المنسدلة على الخلية النشطة، ويفتح القائمة بنقرة أو Alt+Down، ويعرض الطلب الأصفر للإدخال أسفل الخلية. وتفاصيل الشبكة نفسها مغطاة في مقالة بناء شبكة جداول مخصصة بـ TXLSWorkbookViewer

وضبط CircleInvalidData على True يرسم دوائر حمراء حول القيم المخزنة المخالفة لقاعدتها، مثل أمر Excel بالاسم نفسه. يمسح المساحة المرئية فقط، ويستبعد معظم الخلايا سريعًا عبر FindDataValidationAt، ولا يحيط خلية فارغة أبدًا

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Viewer.Editable := True;
  Viewer.EnforceDataValidation := True;
  Viewer.CircleInvalidData := True;
  Viewer.OnValidationError := ViewerValidationError;
end;

procedure TMainForm.ViewerValidationError(Sender: TObject; Row, Col: Integer;
  const Text: WideString; Style: TXLSViewerValidationErrorStyle;
  const AlertTitle, AlertText: WideString;
  var Action: TXLSViewerValidationAction; var Handled: Boolean);
begin
  StatusBar.SimpleText := AlertTitle + ': ' + AlertText;
  if Style = xveStop then
    Action := xvaRetry    // يعيد فتح المحرر بالنص المرفوض
  else
    Action := xvaAccept;
  Handled := True;        // يكبت الحوار المدمج
end;

اترك Handled على False ويعرض الـ viewer حوار تنبيهه الخاص، وهو يعمل تحت VCL وLCL معًا. ذلك الافتراضي يعض في الاختبارات الآلية: اختبار يقود تحريرات بلا معالج OnValidationError يضبط Handled سينسد على حوار modal. ونمط الـ viewer تعداد من نوع ثالث، ‏TXLSViewerValidationErrorStyle بـ xveStop وxveWarning وxveInformation، متميز عن تعدادي ورقتي العمل

ماذا تعرف قبل الاعتماد على تحقق إدخال HotXLS؟

الفحوص أمينة لقواعد القائمة والعدد والتاريخ والوقت وطول النص، وبضع تفاصيل خاصة بالمحرك تستحق المعرفة:

  • على أوراق عمل Classic XLS، يسجل ValidateCellInput قواعد الصيغ المخصصة صالحة للمرشحين المكتوبين، لأن مرشحًا لا يمكن وضعه في خلية BIFF مؤقتًا دون إزعاج سلسلة الاعتماد. افحص القيمة المخزنة بـ ValidateCellValue التي تقيم الصيغة المخصصة، أو استخدم محرك XLSX الذي يقيّم القواعد المخصصة للمرشحين أيضًا
  • على Classic XLS، يعامل تطابق القائمة القيم المتساوية عدديًا العنصر نفسه، فـ 5 و5.0 كلتاهما تطابق عنصر 5
  • في الـ viewer، المحتوى الملصوق يتخطى التحقق كما في Excel: ‏EditSelectedCell لا محرر لإعادة فتحه، فنتيجة Retry هناك تتصرف كـ Cancel
  • إسنادات Cells[...].Value الصافية لا تفحص أبدًا، وهذا بالتصميم: التحقق يحدث فقط حيث يطلبه كودك

إنشاء القواعد في المقام الأول، بما فيها أنواع العدد الكامل والتاريخ وطول النص والمخصصة، مغطى في دليل HotXLS للتحقق من صحة البيانات وAutoFilter والجداول. وتحمَل مصنفات XLSB إلى TXLSXWorkbook، فكل استدعاء TXLSXWorksheet معروض هنا يعمل عليها دون تغيير، كما في مقالة قراءة وكتابة مصنفات XLSB ثنائية في Delphi

حل الـ dropdown واختيار العناصر والتحقق من الإدخال يأتي في مكوّن HotXLS لـ Excel في Delphi لـ Delphi وC++Builder وLazarus، مع عرض DataValidation وعرض WorkbookViewer اللذين يمارسان كل استدعاء في هذه المقالة