مقاله فنی

یک grid صفحه‌گسترده‌ی سفارشی در Delphi با HotXLS

HotXLS، TXLSWorkbookViewer را عرضه می‌کند، یک کنترل بومی VCL که کاربرگ‌های XLS، XLSX، XLSM، و ODS را به‌عنوان یک grid صفحه‌گسترده‌ی تعاملی درون یک فرم Delphi یا C++Builder رندر می‌کند، بدون نصب اکسل یا هدایت آن از طریق OLE automation. ساخت آن نوع کنترل به‌خوبی یعنی حل سه مسئله‌ی مشخص: نگاشت یک کلیک ماوس که درون یک سلول ادغام‌شده فرود می‌آید به سلول منطقی درست، نگه‌داشتن موقعیت اسکرول، باندهای هدر، و انتخاب سلول به‌طور سازگار همان‌طور که یک کاربر یک برگه‌ی بسیار بزرگ‌تر از پنجره‌ی قابل‌مشاهده را می‌گرداند، و تصمیم‌گیری اینکه یک کلیک روی یک نشانگر نظر یا یک سلول هایپرلینک واقعاً باید چه کاری انجام دهد

اغلب شرکت‌های Delphi به دلایلی به‌سراغ یک نمایشگر صفحه‌گسترده می‌روند که هیچ ربطی به ویرایش ندارد: یک ایستگاه ممیزی که کاربرگ‌های آپلودشده را پیش از ورود به یک خط لوله پیش‌نمایش می‌کند، یک کیوسک یا نمایشگر گزارش که مایکروسافت آفیس بخشی از image استقرار نیست، یا یک ابزار QA که نیاز دارد محتوای یک کاربرگ را بدون غیرقابل‌پیش‌بینی‌بودن خودکارسازی یک فرآیند واقعی اکسل روی COM نشان دهد. یک string grid ساده سریع متن را درون سلول‌ها به شما می‌دهد، اما یک فایل صفحه‌گسترده یک grid ساده نیست: سلول‌ها در بلوک‌هایی ادغام می‌شوند که فقط یک‌بار در مدل زیرین وجود دارند، برگه‌ها باندهای هدر ثابت و موقعیت‌های اسکرول افقی و عمودی مستقل حمل می‌کنند، و سلول‌های تکی نظرات و هایپرلینک‌هایی حمل می‌کنند که به مدل تعامل خودشان نیاز دارند. TXLSWorkbookViewer پاسخ HotXLS به آن شکاف است، و طراحی داخلی‌اش یک نقشه‌ی معقول برای هرکسی است که یک کنترل مشابه را از صفر می‌سازد

یک نمایشگر کاربرگ چطور از وابستگی به اکسل اجتناب می‌کند؟

TXLSWorkbookViewer کاملاً از اکسل اجتناب می‌کند با خواندن از طریق مدل شیء تجزیه‌شده‌ی خودِ HotXLS به‌جای بازکردن یک سند از طریق اکسل و puppeteering آن. ویژگی Workbook یک TXLSWorkbook موجود را برای فایل‌های XLS کلاسیک پیوند می‌دهد، و XlsxWorkbook یک TXLSXWorkbook را برای انواع XLSX، XLSM، و الگو پیوند می‌دهد؛ هرکدام می‌تواند از پیش جای دیگری در برنامه باز باشد، و نمایشگر فقط از آن می‌خواند. وقتی کنترل باید خودِ فایل را در اختیار داشته باشد، LoadFromFile پسوند را بازرسی می‌کند، XLSX، XLSM، XLTX، XLTM، و ODS را از طریق موتور مدرن و هر چیز دیگری را از طریق موتور کلاسیک هدایت می‌کند، و هر کاربرگی که خودش ساخته را به‌محض اینکه کنترل پاک یا نابود شود آزاد می‌کند

var
  Viewer: TXLSWorkbookViewer;
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  if Book.Open('quarterly-report.xlsx') <> 1 then
    raise Exception.Create('Could not open workbook');

  Viewer := TXLSWorkbookViewer.Create(Self);
  Viewer.Parent := Self;
  Viewer.Align := alClient;
  Viewer.XlsxWorkbook := Book;        // the viewer does not take ownership
  Viewer.GoToCell(1, 1);

  Caption := Viewer.WorksheetName + ': ' + Viewer.SelectedCellText;
end;

یافتن سلول درست درون یک بازه‌ی ادغام‌شده

حل یک کلیک به سلول درست در TXLSWorkbookViewer یک جستجوی دومرحله‌ای است، و این تفکیک اهمیت دارد چون هندسه‌ی پیکسل و معناشناسی صفحه‌گسترده واقعاً مسائل متفاوتی هستند. مرحله‌ی اول هندسه‌ی خالص است: یک متد خصوصی CellAtPoint عرض ستون‌ها و ارتفاع ردیف‌ها را از موقعیت اسکرول فعلی می‌پیماید تا باندی را بیابد که مختصات X و Y کلیک‌شده را در بر می‌گیرد، بدون هیچ آگاهی‌ای از سلول‌های ادغام‌شده اصلاً. مرحله‌ی دوم معناشناختی است: هر مسیری که انتخاب را تغییر دهد، یک کلیک ماوس، یک کلید فلش، Tab، یا یک فراخوانی مستقیم به GoToCell، از طریق یک روال داخلی تکی ChangeSelection سرازیر می‌شود، که ردیف و ستون خام را در برابر هر ادغامی نرمال‌سازی می‌کند و آن‌ها را به سلول لنگر ادغام می‌چسباند پیش از اینکه انتخاب واقعاً تغییر کند

لنگر سلول بالا-چپ بازه‌ی ادغام‌شده است، و تنها سلول در آن بلوک است که واقعاً یک مقدار، یک قالب، یک نظر، یا یک هایپرلینک در مدل کاربرگ زیرین نگه می‌دارد؛ هر سلول دیگری که ادغام از نظر بصری می‌پوشاند در خودِ داده خالی است. برای کاربرگ‌های XLS کلاسیک، لنگر از Cell.MergeArea می‌آید، یک IXLSRange که Row و Columnاش به سلول مالک اشاره می‌کنند؛ برای کاربرگ‌های XLSX و ODS، MergedCells.FindAt یک TXLSXMergedRange برمی‌گرداند که همان لنگر را به‌عنوان Row1 و Col1 در معرض دید می‌گذارد. رسم مستقلاً یک مسئله‌ی معادل را حل می‌کند، و مستطیل یک سلول ادغام‌شده را به کل بازه‌ی ردیف و ستونش گسترش می‌دهد و از سلول‌های درون آن بازه رد می‌شود، پس طرح‌کلی انتخاب کل بلوک ادغام‌شده را می‌پیچد نه صرفاً گوشه‌ی لنگرش، و نوشتن چیدمان‌های ادغام‌شده به‌جای صرفاً دوباره‌خواندنشان یک مسئله‌ی مرتبط اما متمایز است که در مقاله‌ی همراه درباره‌ی چیدمان سلول-ادغام‌شده برای الگوهای گزارش پوشش داده شده

var
  Sheet: TXLSXWorksheet;
begin
  Sheet := Book.Sheets.Add('Summary');
  Sheet.MergeCells(2, 2, 3, 4);       // B2:D3
  Sheet.Cells[2, 2].Value := 'Region totals';

  Viewer.XlsxWorkbook := Book;
  Viewer.GoToCell(3, 4);              // targets the bottom-right corner of the merge
  // SelectedRow is now 2 and SelectedCol is now 2: normalized to the anchor cell
end;

چه چیزی اسکرول، هدرها، و انتخاب را همگام نگه می‌دارد؟

TXLSWorkbookViewer سه تکه‌ی جداگانه از وضعیت را منسجم نگه می‌دارد: موقعیت اسکرول منطقی که در TopRow و LeftCol نگه‌داشته می‌شود، اسکرول‌بارهای بومی ویندوز که کنترل از طریق WS_HSCROLL و WS_VSCROLL در CreateParams درخواست می‌کند، و انتخاب فعلی در SelectedRow و SelectedCol. کشیدن یک اسکرول‌بار یا چرخاندن چرخ ماوس WM_HSCROLL، WM_VSCROLL، یا WM_MOUSEWHEEL را شلیک می‌کند، که TopRow یا LeftCol را به‌روز می‌کند و دوباره رسم می‌کند؛ انتخاب حرکت نمی‌کند، که با اینکه خودِ اکسل هدایت را از انتخاب جدا می‌کند مطابقت دارد. پس از هرکدام از آن به‌روزرسانی‌ها، UpdateScrollBars موقعیت جدید را از طریق SetScrollInfo به اسکرول‌بار بومی برمی‌گرداند، پس شست هرگز از توافق با آنچه grid واقعاً نشان می‌دهد منحرف نمی‌شود

ناوبری صفحه‌کلید همان همگام‌سازی را در جهت مخالف اجرا می‌کند: حرکت انتخاب از لبه‌ی grid قابل‌مشاهده EnsureSelectionVisible را فرا می‌خواند، که TopRow یا LeftCol را با انباشت عرض ستون‌ها و ارتفاع ردیف‌های واقعی به‌جای صرفاً افزایش یکی‌یکی هُل می‌دهد، چون ردیف‌ها و ستون‌ها می‌توانند اندازه‌های سفارشی حمل کنند، و سپس UpdateScrollBars را فرا می‌خواند پس شست هرجایی که صفحه‌کلید همین الان نما را برده منعکس می‌کند. باندهای هدر شماره‌ی-ردیف و حرف-ستون، اندازه‌گذاری‌شده از طریق RowHeaderWidth و ColumnHeaderHeight، بخشی از این کنترل هستند که روی صفحه ثابت می‌مانند درحالی‌که TopRow و LeftCol داده‌ی زیرین را اسکرول می‌کنند، و این حد فریزکردنی است که این کنترل به‌تنهایی انجام می‌دهد: این ویژگی Freeze Panes اکسل نیست، و هیچ راه توکاری برای سنجاق‌کردن یک ردیف یا ستون داده‌ی دلخواه درحالی‌که بقیه‌ی برگه از آن عبور می‌کند وجود ندارد. یک مرز که ارزش تست‌کردن دارد پیش از عرضه‌ی یک نمایشگر روی فایل‌هایی که کاملاً کنترلشان نمی‌کنید این است که TopRow و LeftCol در برابر بازه‌ی استفاده‌شده‌ی واقعی کاربرگ محدود نمی‌شوند، پس یک شست کشیده‌شده به حد ساختاری‌اش می‌تواند روی ردیف ۱٬۰۴۸٬۵۷۶ یا ستون ۱۶٬۳۸۴ فرود بیاید و یک grid خالی نشان دهد به‌جای آخرین ردیف یا ستونی که واقعاً داده دارد؛ کاربرگ‌هایی که به‌اندازه‌ی کافی بزرگ‌اند که این را قابل‌توجه کنند معمولاً به‌اندازه‌ی کافی بزرگ‌اند که به توجه سمت-بارگذاری پوشش‌داده‌شده در مقاله‌ی کارایی کاربرگ بزرگ هم نیاز داشته باشند

سیم‌کشی نظرات و هایپرلینک‌ها به رویدادهای ماوس و انتخاب

TXLSWorkbookViewer نظرات و هایپرلینک‌ها را به‌عنوان ویژگی‌های هر سلولی که در حال حاضر انتخاب شده در نظر می‌گیرد نه به‌عنوان هدف‌های hover، پس SelectedCellCommentText، SelectedCellCommentAuthor، و SelectedCellHyperlink هر بار که OnSelectionChange شلیک شود به‌روزرسانی می‌شوند، چه انتخاب با کلیک ماوس حرکت کرده باشد، چه کلید فلش، چه یک فراخوانی به GoToCell. یک سلول نظردار یک مثلث قرمز کوچک در گوشه‌ی بالا-راستش رسم می‌شود به‌عنوان یک نشانه‌ی بصری، شبیه به پرچم نظر خودِ اکسل، اما آن نشانگر کاملاً بصری است؛ هیچ tooltip ماشه‌شده‌با-hover توکاری در کنترل وجود ندارد، پس برنامه‌ای که یک popup روی mouse-over به‌جای روی انتخاب می‌خواهد، باید آن لایه را خودش بسازد. فعال‌سازی هایپرلینک به همان روش انتخاب-اول کار می‌کند: دابل‌کلیک‌کردن روی یک سلول ActivateSelectedCell را فرا می‌خواند، که SelectedCellHyperlink را می‌خواند و، اگر خالی نباشد، OnHyperlinkClick را با آدرس هدف و یک پارامتر var Handled: Boolean برای تنظیم توسط handler raise می‌کند

آنچه OnHyperlinkClick انجام نمی‌دهد به همان اندازه اهمیت دارد: TXLSWorkbookViewer هرگز ShellExecute را فرا نمی‌خواند یا یک مرورگر را خودش باز نمی‌کند، صرف‌نظر از اینکه handler مقدار Handled را true تنظیم کند یا false رها کند. ناوبری، و هر تصمیمی درباره‌ی اینکه چه چیزی یک هدف امن به‌حساب می‌آید، کاملاً مسئولیت برنامه‌ی میزبان است، که پیش‌فرض درستی برای یک کامپوننت است که هیچ ایده‌ای ندارد آیا درون یک ابزار داخلی مورد اعتماد جاسازی شده یا یک نمایشگر برای فایل‌هایی که یک مشتری همین الان آپلود کرده

procedure TMainForm.ViewerSelectionChange(Sender: TObject; Row, Col: Integer);
begin
  if Viewer.SelectedCellCommentText <> '' then
    StatusBar.SimpleText := Viewer.SelectedCellCommentAuthor + ': ' +
      Viewer.SelectedCellCommentText
  else
    StatusBar.SimpleText := Viewer.SelectedCellHyperlink;
end;

procedure TMainForm.ViewerHyperlinkClick(Sender: TObject;
  const Target: WideString; var Handled: Boolean);
begin
  ShellExecute(0, 'open', PWideChar(Target), nil, nil, SW_SHOWNORMAL);
  Handled := True;
end;

دامنه‌ی انتخاب و محدودیت‌های ناوبری صفحه‌کلید

انتخاب در TXLSWorkbookViewer همیشه یک سلول منطقی تکی است، ردیابی‌شده به‌عنوان SelectedRow و SelectedCol؛ هیچ انتخاب بازه‌ی چندسلولی مستطیلی در کنترل پایه وجود ندارد، پس هر ویژگی‌ای که نیاز داشته باشد روی یک بلوک از سلول‌ها عمل کند باید بالای آن ساخته شود نه از یک شیء انتخاب خوانده شود. پوشش صفحه‌کلید عمداً ابتدایی است: کلیدهای فلش یک سلول در یک زمان حرکت می‌کنند، Home به ابتدای ردیف یا، با Ctrl، به سلول A1 برمی‌گردد، Page Up و Page Down ده ردیف می‌پرند، و Tab و Shift+Tab در سراسر ستون‌ها گام برمی‌دارند؛ هیچ پرش Ctrl+فلش به لبه‌ی یک ناحیه‌ی داده و هیچ انتخاب بازه‌ی گسترده‌شده-با-Shift وجود ندارد، پس کاربرانی که مستقیم از اکسل می‌آیند این شکاف را روی یک برگه‌ی متراکم متوجه می‌شوند

محدودیت‌های ستون در همان نقطه‌ی تنگنای ChangeSelectionای که نرمال‌سازی ادغام را مدیریت می‌کند اعمال می‌شوند، و عمداً بر اساس موتور فرق می‌کنند: یک نمایشگر پیوندشده به یک TXLSWorkbook کلاسیک روی ستون ۲۵۶ گیر می‌کند، سقف ساختاری قالب BIFF8، درحالی‌که یکی پیوندشده به TXLSXWorkbook محدودیت مدرن ۱۶٬۳۸۴ستونی را که XLSX از اکسل ۲۰۰۷ به بعد به ارث برده رعایت می‌کند. ردیف‌ها در هر دو حالت به ۱٬۰۴۸٬۵۷۶ محدود هستند، پس تفاوت عملی بین بازکردن یک فایل XLS قدیمی و یک فایل XLSX در همان نمایشگر کاملاً درباره‌ی اینست که grid تا کجا حاضر است اجازه دهد به سمت راست بروید

هیچ‌کدام از این‌ها عجیب نیست وقتی به جستجوی پیکسل، نرمال‌سازی لنگر، و مشتی handler پیام تجزیه شود، اما وادار کردن این سه به توافق زیر فایل‌های واقعی، با ادغام‌های واقعی، نظرات، و هایپرلینک‌ها، بیشتر کار در کامپوننتی مثل این است. TXLSWorkbookViewer به‌عنوان بخشی از کامپوننت استاندارد Excel از HotXLS برای Delphi و C++Builder عرضه می‌شود، در کنار مدل‌های شیء کلاسیک و XLSXای که از آن‌ها رندر می‌کند