HotXLS постачає TXLSWorkbookViewer — рідний елемент керування VCL, що рендерить книги XLS, XLSX, XLSM та ODS як інтерактивну сітку електронної таблиці всередині форми Delphi чи C++Builder, без встановлення Excel чи керування ним через автоматизацію OLE. Добре побудувати такий елемент керування означає розв'язати три конкретні проблеми: зіставити клацання миші, що потрапляє всередину об'єднаної клітинки, з правильною логічною клітинкою; тримати позицію прокрутки, смуги заголовків і вибір клітинки узгодженими, поки користувач панорамує аркуш, значно більший за видиме вікно; та вирішити, що насправді має робити клацання на маркері коментаря чи клітинці гіперпосилання
Більшість команд Delphi звертаються до переглядача електронних таблиць з причин, що не мають нічого спільного з редагуванням: станція аудиту, що показує попередній перегляд завантажених книг перед тим, як вони потраплять у конвеєр, кіоск чи переглядач звітів, де Microsoft Office не є частиною образу розгортання, чи інструмент контролю якості, якому потрібно показати вміст книги без непередбачуваності автоматизації справжнього процесу Excel через COM. Звичайна рядкова сітка швидко дає вам текст у клітинках, але файл електронної таблиці — не звичайна сітка: клітинки об'єднуються в блоки, що існують лише один раз у базовій моделі, аркуші несуть фіксовані смуги заголовків і незалежні позиції горизонтальної та вертикальної прокрутки, а окремі клітинки несуть коментарі й гіперпосилання, яким потрібна власна модель взаємодії. TXLSWorkbookViewer — відповідь HotXLS на цю прогалину, і його внутрішній дизайн — розумний план для будь-кого, хто будує подібний елемент керування з нуля
Як переглядач книги уникає залежності від Excel?
TXLSWorkbookViewer повністю уникає Excel, читаючи через власну розібрану об'єктну модель HotXLS замість того, щоб відкривати документ через Excel і керувати ним ляльководом. Властивість 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, рідні смуги прокрутки Windows, які елемент керування запитує через WS_HSCROLL та WS_VSCROLL у CreateParams, та поточний вибір у SelectedRow та SelectedCol. Перетягування смуги прокрутки чи прокручування колеса миші спричиняє WM_HSCROLL, WM_VSCROLL чи WM_MOUSEWHEEL, що оновлюють TopRow чи LeftCol і перемальовують; вибір не рухається, що відповідає тому, як сам Excel розділяє панорамування й вибір. Після будь-якого з цих оновлень UpdateScrollBars проштовхує нову позицію назад у рідну смугу прокрутки через SetScrollInfo, тож повзунок ніколи не розходиться з тим, що сітка насправді показує
Навігація клавіатурою виконує ту саму синхронізацію у зворотному напрямку: переміщення вибору за межу видимої сітки викликає EnsureSelectionVisible, який підштовхує TopRow чи LeftCol, накопичуючи фактичні ширини стовпців і висоти рядків, а не просто збільшуючи на одиницю, оскільки рядки та стовпці можуть мати користувацькі розміри, а потім викликає UpdateScrollBars, щоб повзунок відображав те, куди клавіатура щойно перенесла перегляд. Смуги заголовків із номерами рядків і літерами стовпців, розмір яких визначають RowHeaderWidth та ColumnHeaderHeight, — та частина цього елемента керування, що залишається фіксованою на екрані, поки TopRow та LeftCol прокручують дані під нею, і це весь обсяг «заморожування», який цей елемент керування робить самостійно: це не функція «Закріпити області» Excel, і немає вбудованого способу закріпити довільний рядок чи стовпець даних, поки решта аркуша прокручується повз нього. Одна межа, яку варто перевірити перед відправкою переглядача над файлами, які ви не повністю контролюєте, — TopRow та LeftCol не обмежені реальним використаним діапазоном аркуша, тож повзунок, перетягнутий до своєї структурної межі, може приземлитися на рядок 1 048 576 чи стовпець 16 384 й показати порожню сітку замість останнього рядка чи стовпця, що справді містить дані; книги, достатньо великі, щоб це стало помітним, зазвичай також достатньо великі, щоб потребувати уваги на боці завантаження, розглянутої в статті про продуктивність великих книг
Підключення коментарів і гіперпосилань до подій миші та вибору
TXLSWorkbookViewer трактує коментарі та гіперпосилання як атрибути тієї клітинки, яка наразі вибрана, а не як цілі наведення курсору, тож SelectedCellCommentText, SelectedCellCommentAuthor та SelectedCellHyperlink оновлюються щоразу, коли спрацьовує OnSelectionChange, незалежно від того, чи вибір перемістився клацанням миші, клавішею зі стрілкою чи викликом GoToCell. Клітинка з коментарем отримує невеликий червоний трикутник, намальований у верхньому правому куті, як візуальний натяк, схожий на власний прапорець коментаря Excel, але цей маркер суто візуальний; у елемент керування не вбудовано підказку, викликану наведенням курсору, тож застосунок, що хоче спливаюче вікно при наведенні, а не при виборі, мусить будувати цей шар сам. Активація гіперпосилання працює тим самим способом, орієнтованим на вибір: подвійне клацання на клітинці викликає ActivateSelectedCell, який читає SelectedCellHyperlink і, якщо він не порожній, піднімає OnHyperlinkClick із цільовою адресою та параметром var Handled: Boolean, який обробник має встановити
Те, чого OnHyperlinkClick не робить, так само важливо: TXLSWorkbookViewer ніколи сам не викликає ShellExecute й не відкриває браузер, незалежно від того, чи обробник встановлює 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, тож користувачі, що прийшли прямо з Excel, помітять цю прогалину на щільному аркуші
Обмеження стовпців застосовуються в тій самій точці звуження ChangeSelection, що обробляє нормалізацію об'єднання, і вони навмисно різняться за рушієм: переглядач, прив'язаний до класичного TXLSWorkbook, обмежується стовпцем 256, структурною стелею формату BIFF8, тоді як прив'язаний до TXLSXWorkbook дотримується сучасного ліміту 16 384 стовпців, який XLSX успадкував від Excel 2007 і далі. Рядки в обох випадках обмежені 1 048 576, тож практична різниця між відкриттям застарілого файлу XLS та файлу XLSX у тому самому переглядачі повністю в тому, наскільки далеко вправо сітка готова вас пустити
Ніщо з цього не екзотичне, щойно розкладене на пошук за пікселем, нормалізацію якоря та жменю обробників повідомлень, але змусити ці три узгодитися на реальних файлах, зі справжніми об'єднаннями, коментарями та гіперпосиланнями, — більшість роботи в компоненті на кшталт цього. TXLSWorkbookViewer постачається як частина стандартного компонента HotXLS Excel для Delphi та C++Builder, поряд з класичною та XLSX об'єктними моделями, з яких він рендерить