HotXLS gửi kèm TXLSWorkbookViewer, một điều khiển VCL gốc render workbook XLS, XLSX, XLSM, và ODS thành một grid bảng tính tương tác bên trong một form Delphi hay C++Builder, không cần cài Excel hay điều khiển nó qua OLE automation. Xây dựng tốt loại điều khiển đó có nghĩa là giải quyết ba vấn đề cụ thể: ánh xạ một cú nhấp chuột đáp xuống bên trong một ô đã gộp về đúng ô logic, giữ vị trí cuộn, dải header, và việc chọn ô nhất quán khi người dùng lia qua một sheet lớn hơn nhiều so với cửa sổ hiển thị, và quyết định một cú nhấp vào một dấu comment hay một ô siêu liên kết nên thực sự làm gì
Hầu hết các hãng Delphi tìm đến một trình xem bảng tính vì những lý do không liên quan gì đến việc chỉnh sửa: một trạm kiểm toán xem trước các workbook được tải lên trước khi chúng vào một pipeline, một kiosk hay trình xem báo cáo nơi Microsoft Office không phải một phần của image triển khai, hay một công cụ QA cần hiển thị nội dung một workbook mà không có sự khó lường của việc tự động hóa một tiến trình Excel thật qua COM. Một string grid thuần túy cho bạn văn bản trong ô nhanh chóng, nhưng một file bảng tính không phải một grid thuần túy: các ô gộp lại thành khối chỉ tồn tại một lần trong mô hình bên dưới, các sheet mang dải header cố định và vị trí cuộn ngang và dọc độc lập, và các ô riêng lẻ mang comment và siêu liên kết cần mô hình tương tác riêng của chúng. TXLSWorkbookViewer là câu trả lời của HotXLS cho khoảng trống đó, và thiết kế nội bộ của nó là một bản thiết kế hợp lý cho bất kỳ ai xây dựng một điều khiển tương tự từ đầu
Một trình xem workbook tránh phụ thuộc vào Excel như thế nào?
TXLSWorkbookViewer hoàn toàn tránh Excel bằng cách đọc qua mô hình đối tượng đã phân tích riêng của HotXLS thay vì mở một tài liệu thông qua Excel và điều khiển nó như một con rối. Thuộc tính Workbook ràng buộc một TXLSWorkbook đã tồn tại cho các file XLS cổ điển, và XlsxWorkbook ràng buộc một TXLSXWorkbook cho các biến thể XLSX, XLSM, và template; cả hai đều có thể đã mở sẵn ở nơi khác trong ứng dụng, và trình xem chỉ đọc từ đó. Khi điều khiển nên tự sở hữu file, LoadFromFile kiểm tra phần mở rộng, định tuyến XLSX, XLSM, XLTX, XLTM, và ODS qua engine hiện đại và mọi thứ khác qua engine cổ điển, và giải phóng bất cứ workbook nào nó đã tạo một khi điều khiển bị xóa hoặc hủy
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;
Định vị đúng ô bên trong một vùng đã gộp
Giải quyết một cú nhấp thành đúng ô trong TXLSWorkbookViewer là một lượt tra cứu hai giai đoạn, và sự tách bạch đó quan trọng vì hình học pixel và ngữ nghĩa bảng tính thực sự là những vấn đề khác nhau. Giai đoạn đầu là hình học thuần túy: một phương thức CellAtPoint private duyệt qua chiều rộng cột và chiều cao hàng từ vị trí cuộn hiện tại cho đến khi nó tìm thấy dải chứa tọa độ X và Y đã nhấp, hoàn toàn không nhận biết về ô đã gộp. Giai đoạn hai là ngữ nghĩa: mọi đường thay đổi vùng chọn, một cú nhấp chuột, một phím mũi tên, Tab, hay một lệnh gọi trực tiếp đến GoToCell, đều đi qua một thủ tục ChangeSelection nội bộ duy nhất, thủ tục này chuẩn hóa hàng và cột thô theo bất kỳ gộp nào và chốt chúng về ô neo của gộp trước khi vùng chọn thực sự thay đổi
Ô neo là ô trên-cùng-bên-trái của vùng đã gộp, và nó là ô duy nhất trong khối đó thực sự mang một giá trị, một định dạng, một comment, hay một siêu liên kết trong mô hình workbook bên dưới; mọi ô khác mà vùng gộp bao phủ về mặt thị giác đều rỗng trong chính dữ liệu. Với workbook XLS cổ điển, ô neo đến từ Cell.MergeArea, một IXLSRange mà Row và Column của nó trỏ vào ô sở hữu; với workbook XLSX và ODS, MergedCells.FindAt trả về một TXLSXMergedRange phơi bày cùng ô neo dưới dạng Row1 và Col1. Việc vẽ giải quyết một vấn đề tương đương một cách độc lập, mở rộng hình chữ nhật của một ô đã gộp đến toàn bộ phạm vi hàng và cột của nó và bỏ qua các ô bên trong phạm vi đó, để đường viền vùng chọn bao trọn toàn bộ khối gộp thay vì chỉ góc neo của nó, và việc viết các bố cục đã gộp thay vì chỉ đọc lại chúng là một vấn đề liên quan nhưng riêng biệt được nói đến trong bài viết đồng hành về bố cục ô gộp cho mẫu báo cáo
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;
Điều gì giữ việc cuộn, header, và vùng chọn đồng bộ?
TXLSWorkbookViewer giữ ba mảnh trạng thái riêng biệt nhất quán: vị trí cuộn logic giữ trong TopRow và LeftCol, các thanh cuộn Windows gốc mà điều khiển yêu cầu thông qua WS_HSCROLL và WS_VSCROLL trong CreateParams, và vùng chọn hiện tại trong SelectedRow và SelectedCol. Kéo một thanh cuộn hay xoay con lăn chuột phát ra WM_HSCROLL, WM_VSCROLL, hoặc WM_MOUSEWHEEL, cập nhật TopRow hoặc LeftCol và vẽ lại; vùng chọn không di chuyển, khớp với cách chính Excel tách biệt việc lia khỏi việc chọn. Sau bất kỳ cập nhật nào trong số đó, UpdateScrollBars đẩy vị trí mới trở lại thanh cuộn gốc thông qua SetScrollInfo, nên con trượt không bao giờ trôi dạt khỏi sự đồng thuận với những gì grid thực sự đang hiển thị
Điều hướng bàn phím chạy cùng sự đồng bộ đó theo chiều ngược lại: di chuyển vùng chọn qua khỏi cạnh của grid hiển thị gọi EnsureSelectionVisible, hàm này đẩy TopRow hoặc LeftCol bằng cách tích lũy chiều rộng cột và chiều cao hàng thực tế thay vì đơn giản tăng lên một, vì hàng và cột có thể mang kích thước tùy chỉnh, và sau đó gọi UpdateScrollBars để con trượt phản ánh bất cứ nơi nào bàn phím vừa đưa view đến. Các dải header số hàng và chữ cột, có kích thước qua RowHeaderWidth và ColumnHeaderHeight, là phần của điều khiển này giữ cố định trên màn hình trong khi TopRow và LeftCol cuộn dữ liệu bên dưới, và đó là phạm vi đóng băng mà điều khiển này tự làm: nó không phải tính năng Freeze Panes của Excel, và không có cách tích hợp sẵn nào để ghim một hàng hay cột dữ liệu tùy ý trong khi phần còn lại của sheet cuộn qua nó. Một ranh giới đáng test trước khi phát hành một trình xem trên các file bạn không hoàn toàn kiểm soát là TopRow và LeftCol không bị kẹp theo phạm vi sử dụng thực tế của worksheet, nên một con trượt bị kéo đến giới hạn cấu trúc của nó có thể đáp xuống hàng 1.048.576 hay cột 16.384 và hiển thị một grid trống thay vì hàng hay cột cuối cùng thực sự chứa dữ liệu; các workbook đủ lớn để khiến điều này đáng chú ý thường cũng đủ lớn để cần sự chú ý phía nạp được nói đến trong bài viết về hiệu năng workbook lớn
Kết nối comment và siêu liên kết với các sự kiện chuột và vùng chọn
TXLSWorkbookViewer coi comment và siêu liên kết như thuộc tính của bất kỳ ô nào đang được chọn thay vì như các mục tiêu hover, nên SelectedCellCommentText, SelectedCellCommentAuthor, và SelectedCellHyperlink cập nhật mỗi lần OnSelectionChange phát ra, dù vùng chọn di chuyển bằng cú nhấp chuột, phím mũi tên, hay một lệnh gọi đến GoToCell. Một ô có comment được vẽ một tam giác đỏ nhỏ ở góc trên-phải như một gợi ý thị giác, tương tự cờ comment riêng của Excel, nhưng dấu đó thuần túy chỉ mang tính thị giác; không có tooltip kích hoạt bằng hover nào tích hợp sẵn trong điều khiển, nên một ứng dụng muốn một popup khi di chuột thay vì khi chọn phải tự xây dựng lớp đó. Việc kích hoạt siêu liên kết hoạt động theo cùng cách chọn-trước: nhấp đúp vào một ô gọi ActivateSelectedCell, hàm này đọc SelectedCellHyperlink và, nếu nó không rỗng, phát ra OnHyperlinkClick với địa chỉ đích và một tham số var Handled: Boolean để handler đặt
Những gì OnHyperlinkClick không làm cũng quan trọng không kém: TXLSWorkbookViewer không bao giờ tự gọi ShellExecute hay mở một trình duyệt, bất kể handler có đặt Handled thành true hay để nó là false. Việc điều hướng, và bất kỳ quyết định nào về điều gì tính là một đích an toàn, hoàn toàn là trách nhiệm của ứng dụng host, đó là mặc định đúng đắn cho một component không có ý niệm gì về việc nó được nhúng trong một công cụ nội bộ đáng tin cậy hay một trình xem cho các file mà một khách hàng vừa tải lên
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;
Phạm vi vùng chọn và giới hạn điều hướng bàn phím
Vùng chọn trong TXLSWorkbookViewer luôn là một ô logic đơn lẻ, được theo dõi dưới dạng SelectedRow và SelectedCol; không có vùng chọn nhiều ô hình chữ nhật nào trong điều khiển cơ bản, nên bất kỳ tính năng nào cần thao tác trên một khối ô phải được xây dựng bên trên nó thay vì đọc từ một đối tượng vùng chọn. Độ bao phủ bàn phím có chủ đích ở mức cơ bản: phím mũi tên di chuyển một ô mỗi lần, Home quay về đầu hàng hoặc, với Ctrl, về ô A1, Page Up và Page Down nhảy mười hàng, và Tab cùng Shift+Tab bước qua các cột; không có bước nhảy Ctrl+Arrow đến cạnh của một vùng dữ liệu và không có vùng chọn mở rộng bằng Shift, nên người dùng đến thẳng từ Excel sẽ nhận thấy khoảng trống đó trên một sheet dày đặc
Giới hạn cột được thực thi tại cùng điểm nghẽn ChangeSelection xử lý việc chuẩn hóa gộp, và chúng khác nhau theo engine một cách có chủ đích: một trình xem ràng buộc với một TXLSWorkbook cổ điển kẹp ở cột 256, trần cấu trúc của định dạng BIFF8, trong khi một trình xem ràng buộc với TXLSXWorkbook tôn trọng giới hạn 16.384 cột hiện đại mà XLSX kế thừa từ Excel 2007 trở đi. Hàng bị giới hạn ở 1.048.576 trong cả hai trường hợp, nên khác biệt thực tế giữa việc mở một file XLS cũ và một file XLSX trong cùng trình xem hoàn toàn nằm ở việc grid sẵn lòng cho bạn đi xa về phía phải đến đâu
Không điều nào trong số này kỳ lạ một khi được chia nhỏ thành tra cứu pixel, chuẩn hóa neo, và một vài trình xử lý message, nhưng khiến ba thứ đó đồng thuận dưới các file thật, với các gộp, comment, và siêu liên kết thật, là phần lớn công việc trong một component như thế này. TXLSWorkbookViewer đi kèm sẵn trong HotXLS Excel Component tiêu chuẩn dành cho Delphi và C++Builder, cùng với các mô hình đối tượng cổ điển và XLSX mà nó render từ đó