HotXLS dostarcza TXLSWorkbookViewer, natywny formant VCL, który renderuje skoroszyty XLS, XLSX, XLSM i ODS jako interaktywną siatkę arkusza kalkulacyjnego wewnątrz formularza Delphi lub C++Builder, bez instalowania Excela ani sterowania nim przez automatyzację OLE. Zbudowanie tego rodzaju formantu dobrze oznacza rozwiązanie trzech konkretnych problemów: zmapowanie kliknięcia myszą, które ląduje wewnątrz scalonej komórki, na właściwą komórkę logiczną, utrzymanie spójności pozycji przewijania, pasm nagłówków i zaznaczenia komórki, gdy użytkownik przesuwa widok po arkuszu znacznie większym niż widoczne okno, oraz zdecydowanie, co powinno faktycznie zrobić kliknięcie znacznika komentarza lub komórki z hiperłączem
Większość firm korzystających z Delphi sięga po przeglądarkę arkuszy kalkulacyjnych z powodów niemających nic wspólnego z edycją: stanowisko audytowe, które podgląda przesłane skoroszyty, zanim wejdą do potoku przetwarzania, kiosk lub przeglądarka raportów, gdzie Microsoft Office nie jest częścią obrazu wdrożeniowego, albo narzędzie QA, które musi pokazać zawartość skoroszytu bez nieprzewidywalności automatyzowania prawdziwego procesu Excela przez COM. Zwykła siatka tekstowa daje szybko tekst w komórkach, ale plik arkusza kalkulacyjnego to nie zwykła siatka: komórki scalają się w bloki, które w bazowym modelu istnieją tylko raz, arkusze niosą stałe pasma nagłówków i niezależne poziome oraz pionowe pozycje przewijania, a poszczególne komórki niosą komentarze i hiperłącza, które potrzebują własnego modelu interakcji. TXLSWorkbookViewer to odpowiedź HotXLS na tę lukę, a jego wewnętrzny projekt to rozsądny plan dla każdego, kto buduje podobny formant od zera
Jak przeglądarka skoroszytów unika zależności od Excela?
TXLSWorkbookViewer całkowicie unika Excela, odczytując dane przez własny sparsowany model obiektowy HotXLS, zamiast otwierać dokument przez Excel i nim sterować. Właściwość Workbook wiąże istniejący TXLSWorkbook dla klasycznych plików XLS, a XlsxWorkbook wiąże TXLSXWorkbook dla wariantów XLSX, XLSM i szablonów; każdy z nich może już być otwarty gdzie indziej w aplikacji, a przeglądarka tylko z niego czyta. Gdy formant sam ma być właścicielem pliku, LoadFromFile sprawdza rozszerzenie, kieruje XLSX, XLSM, XLTX, XLTM i ODS przez nowoczesny silnik, a wszystko inne przez silnik klasyczny, i zwalnia dowolny skoroszyt, który sam utworzył, gdy formant zostanie wyczyszczony lub zniszczony
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;
Lokalizowanie właściwej komórki wewnątrz scalonego zakresu
Rozwiązanie kliknięcia do właściwej komórki w TXLSWorkbookViewer to dwuetapowe wyszukiwanie, i ten podział ma znaczenie, ponieważ geometria pikseli i semantyka arkusza kalkulacyjnego to naprawdę różne problemy. Pierwszy etap to czysta geometria: prywatna metoda CellAtPoint przechodzi przez szerokości kolumn i wysokości wierszy od bieżącej pozycji przewijania, aż znajdzie pasmo zawierające kliknięte współrzędne X i Y, w ogóle bez świadomości scalonych komórek. Drugi etap jest semantyczny: każda ścieżka, która zmienia zaznaczenie — kliknięcie myszą, klawisz strzałki, Tab lub bezpośrednie wywołanie GoToCell — przechodzi przez jedną wewnętrzną procedurę ChangeSelection, która normalizuje surowy wiersz i kolumnę względem dowolnego scalenia i przyciąga je do komórki kotwiczącej scalenia, zanim zaznaczenie faktycznie się zmieni
Kotwica to komórka w lewym górnym rogu scalonego zakresu, i jest to jedyna komórka w tym bloku, która naprawdę przechowuje wartość, format, komentarz lub hiperłącze w bazowym modelu skoroszytu; każda inna komórka wizualnie objęta scaleniem jest pusta w samych danych. Dla klasycznych skoroszytów XLS kotwica pochodzi z Cell.MergeArea, IXLSRange, którego Row i Column wskazują na komórkę właścicielską; dla skoroszytów XLSX i ODS MergedCells.FindAt zwraca TXLSXMergedRange, udostępniający tę samą kotwicę jako Row1 i Col1. Rysowanie rozwiązuje równoważny problem niezależnie, rozszerzając prostokąt scalonej komórki na jej pełny zasięg wierszy i kolumn oraz pomijając komórki wewnątrz tego zasięgu, tak aby kontur zaznaczenia obejmował cały scalony blok, a nie tylko jego róg kotwiczący, a zapisywanie scalonych układów, a nie tylko odczytywanie ich z powrotem, to powiązany, ale odrębny problem, omówiony w towarzyszącym artykule o układzie scalonych komórek dla szablonów raportów
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;
Co utrzymuje przewijanie, nagłówki i zaznaczenie w synchronizacji?
TXLSWorkbookViewer utrzymuje spójność trzech osobnych elementów stanu: logicznej pozycji przewijania przechowywanej w TopRow i LeftCol, natywnych pasków przewijania Windows, o które formant prosi przez WS_HSCROLL i WS_VSCROLL w CreateParams, oraz bieżącego zaznaczenia w SelectedRow i SelectedCol. Przeciągnięcie paska przewijania lub obrócenie kółka myszy uruchamia WM_HSCROLL, WM_VSCROLL lub WM_MOUSEWHEEL, które aktualizują TopRow lub LeftCol i przerysowują widok; zaznaczenie się nie przesuwa, co odpowiada temu, jak sam Excel oddziela przesuwanie widoku od zaznaczania. Po każdej z tych aktualizacji UpdateScrollBars wpycha nową pozycję z powrotem do natywnego paska przewijania przez SetScrollInfo, więc suwak nigdy nie oddala się od stanu tego, co siatka faktycznie pokazuje
Nawigacja klawiaturą uruchamia tę samą synchronizację w przeciwnym kierunku: przesunięcie zaznaczenia poza krawędź widocznej siatki wywołuje EnsureSelectionVisible, które przesuwa TopRow lub LeftCol, sumując rzeczywiste szerokości kolumn i wysokości wierszy, zamiast po prostu zwiększać o jeden, ponieważ wiersze i kolumny mogą mieć niestandardowe rozmiary, a następnie wywołuje UpdateScrollBars, aby suwak odzwierciedlał to, dokąd klawiatura właśnie przeniosła widok. Pasma nagłówków numerów wierszy i liter kolumn, rozmiarowane przez RowHeaderWidth i ColumnHeaderHeight, to część tego formantu, która pozostaje nieruchoma na ekranie, podczas gdy TopRow i LeftCol przewijają dane pod nią, i to jest cały zakres zamrażania, jaki ten formant robi sam z siebie: to nie jest funkcja Freeze Panes z Excela, i nie ma wbudowanego sposobu, by przypiąć dowolny wiersz lub kolumnę danych, podczas gdy reszta arkusza przewija się pod nią. Jedna granica warta przetestowania przed wdrożeniem przeglądarki dla plików, nad którymi nie masz pełnej kontroli, to fakt, że TopRow i LeftCol nie są ograniczone do rzeczywiście używanego zakresu arkusza, więc suwak przeciągnięty do swojego strukturalnego limitu może wylądować na wierszu 1 048 576 lub kolumnie 16 384 i pokazać pustą siatkę zamiast ostatniego wiersza lub kolumny, które faktycznie zawierają dane; skoroszyty na tyle duże, by to było zauważalne, są zwykle na tyle duże, że wymagają też uwagi po stronie wczytywania, opisanej w artykule o wydajności dużych skoroszytów
Podłączanie komentarzy i hiperłączy do zdarzeń myszy i zaznaczenia
TXLSWorkbookViewer traktuje komentarze i hiperłącza jako atrybuty aktualnie zaznaczonej komórki, a nie jako cele najechania kursorem, więc SelectedCellCommentText, SelectedCellCommentAuthor i SelectedCellHyperlink aktualizują się za każdym razem, gdy uruchamia się OnSelectionChange, niezależnie od tego, czy zaznaczenie przesunęło się przez kliknięcie myszą, klawisz strzałki, czy wywołanie GoToCell. Komórka z komentarzem otrzymuje mały czerwony trójkąt narysowany w prawym górnym rogu jako wizualną wskazówkę, podobnie jak własna flaga komentarza Excela, ale ten znacznik jest czysto wizualny; nie ma wbudowanej w formant podpowiedzi wywoływanej najechaniem kursorem, więc aplikacja, która chce wyskakującego okienka przy najechaniu myszą, a nie przy zaznaczeniu, musi zbudować tę warstwę samodzielnie. Aktywacja hiperłącza działa w ten sam sposób oparty najpierw na zaznaczeniu: dwukrotne kliknięcie komórki wywołuje ActivateSelectedCell, które odczytuje SelectedCellHyperlink i, jeśli nie jest puste, uruchamia OnHyperlinkClick z docelowym adresem oraz parametrem var Handled: Boolean, który handler może ustawić
To, czego OnHyperlinkClick nie robi, jest równie ważne: TXLSWorkbookViewer nigdy sam nie wywołuje ShellExecute ani nie otwiera przeglądarki, niezależnie od tego, czy handler ustawi Handled na true, czy zostawi na false. Nawigacja, oraz jakakolwiek decyzja o tym, co liczy się za bezpieczny cel, należy w całości do aplikacji hostującej, co jest właściwym domyślnym zachowaniem dla komponentu, który nie ma pojęcia, czy jest osadzony w zaufanym narzędziu wewnętrznym, czy w przeglądarce plików, które klient właśnie przesłał
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;
Zakres zaznaczenia i ograniczenia nawigacji klawiaturą
Zaznaczenie w TXLSWorkbookViewer to zawsze pojedyncza komórka logiczna, śledzona jako SelectedRow i SelectedCol; w bazowym formancie nie ma prostokątnego zaznaczenia wielu komórek naraz, więc każda funkcja, która musi działać na bloku komórek, musi zostać zbudowana ponad nim, a nie odczytana z obiektu zaznaczenia. Pokrycie klawiaturą jest celowo podstawowe: klawisze strzałek przesuwają o jedną komórkę naraz, Home wraca do początku wiersza lub, z Ctrl, do komórki A1, Page Up i Page Down przeskakują o dziesięć wierszy, a Tab i Shift+Tab przechodzą między kolumnami; nie ma skoku Ctrl+strzałka do krawędzi obszaru danych ani rozszerzania zakresu przez Shift, więc użytkownicy przychodzący prosto z Excela zauważą tę lukę na gęstym arkuszu
Limity kolumn są egzekwowane w tym samym punkcie kontrolnym ChangeSelection, który obsługuje normalizację scalenia, i celowo różnią się w zależności od silnika: przeglądarka związana z klasycznym TXLSWorkbook ogranicza do kolumny 256, strukturalnego sufitu formatu BIFF8, podczas gdy związana z TXLSXWorkbook honoruje nowoczesny limit 16 384 kolumn, który XLSX odziedziczył od Excela 2007 i nowszych. Wiersze są ograniczone do 1 048 576 w obu przypadkach, więc praktyczna różnica między otwarciem przestarzałego pliku XLS a pliku XLSX w tej samej przeglądarce dotyczy w całości tego, jak daleko w prawo siatka pozwoli się posunąć
Nic z tego nie jest egzotyczne, gdy rozbije się to na wyszukiwanie po pikselach, normalizację kotwicy i garść handlerów komunikatów, ale doprowadzenie tej trójki do zgodności przy prawdziwych plikach, z prawdziwymi scaleniami, komentarzami i hiperłączami, to większość pracy przy komponencie tego rodzaju. TXLSWorkbookViewer jest dostarczany jako część standardowego komponentu Excel HotXLS dla Delphi i C++Buildera, obok klasycznego i XLSX modelu obiektowego, z którego renderuje