Τεχνικό Άρθρο

Ένα προσαρμοσμένο πλέγμα υπολογιστικού φύλλου σε Delphi με το HotXLS

Το HotXLS διανέμει το TXLSWorkbookViewer, ένα γηγενές στοιχείο ελέγχου VCL που αποδίδει βιβλία εργασίας XLS, XLSX, XLSM, και ODS ως διαδραστικό πλέγμα υπολογιστικού φύλλου μέσα σε μια φόρμα Delphi ή C++Builder, χωρίς εγκατάσταση του Excel ή οδήγησή του μέσω αυτοματοποίησης OLE. Το να χτίσετε καλά αυτό το είδος στοιχείου ελέγχου σημαίνει να λύσετε τρία συγκεκριμένα προβλήματα: την αντιστοίχιση ενός κλικ ποντικιού που προσγειώνεται μέσα σε ένα συγχωνευμένο κελί στο σωστό λογικό κελί, τη διατήρηση της θέσης κύλισης, των ζωνών επικεφαλίδας, και της επιλογής κελιών συνεπείς καθώς ένας χρήστης μετακινείται σε ένα φύλλο πολύ μεγαλύτερο από το ορατό παράθυρο, και την απόφαση τι πρέπει πράγματι να κάνει ένα κλικ σε έναν δείκτη σχολίου ή σε ένα κελί υπερσυνδέσμου

Οι περισσότερα καταστήματα Delphi καταφεύγουν σε έναν viewer υπολογιστικού φύλλου για λόγους που δεν έχουν καμία σχέση με την επεξεργασία: έναν σταθμό ελέγχου που προεπισκοπεί μεταφορτωμένα βιβλία εργασίας πριν εισέλθουν σε μια διοχέτευση, ένα περίπτερο ή έναν viewer αναφορών όπου το Microsoft Office δεν είναι μέρος της εικόνας ανάπτυξης, ή ένα εργαλείο QA που χρειάζεται να δείξει το περιεχόμενο ενός βιβλίου εργασίας χωρίς την απροβλεψιμότητα της αυτοματοποίησης μιας πραγματικής διεργασίας Excel μέσω COM. Ένα απλό πλέγμα συμβολοσειρών σας δίνει γρήγορα κείμενο σε κελιά, αλλά ένα αρχείο υπολογιστικού φύλλου δεν είναι απλό πλέγμα: τα κελιά συγχωνεύονται σε μπλοκ που υπάρχουν μόνο μία φορά στο υποκείμενο μοντέλο, τα φύλλα φέρουν σταθερές ζώνες επικεφαλίδας και ανεξάρτητες οριζόντιες και κάθετες θέσεις κύλισης, και μεμονωμένα κελιά φέρουν σχόλια και υπερσυνδέσμους που χρειάζονται το δικό τους μοντέλο αλληλεπίδρασης. Το TXLSWorkbookViewer είναι η απάντηση του HotXLS σε εκείνο το κενό, και ο εσωτερικός σχεδιασμός του είναι ένα λογικό σχέδιο για όποιον χτίζει ένα παρόμοιο στοιχείο ελέγχου από την αρχή

Πώς αποφεύγει ένας viewer βιβλίου εργασίας να εξαρτάται από το Excel;

Το TXLSWorkbookViewer αποφεύγει εντελώς το Excel διαβάζοντας μέσα από το δικό του αναλυμένο μοντέλο αντικειμένων του HotXLS αντί να ανοίγει ένα έγγραφο μέσω του Excel και να το χειραγωγεί σαν μαριονέτα. Η ιδιότητα Workbook δεσμεύει ένα υπάρχον TXLSWorkbook για κλασικά αρχεία XLS, και το XlsxWorkbook δεσμεύει ένα TXLSXWorkbook για παραλλαγές XLSX, XLSM, και προτύπων· οποιοδήποτε από τα δύο μπορεί να είναι ήδη ανοιχτό αλλού στην εφαρμογή, και ο viewer απλώς διαβάζει από αυτό. Όταν το στοιχείο ελέγχου πρέπει να κατέχει το ίδιο το αρχείο, το 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 είναι μια αναζήτηση δύο σταδίων, και ο διαχωρισμός έχει σημασία επειδή η γεωμετρία pixel και η σημασιολογία υπολογιστικού φύλλου είναι γνησίως διαφορετικά προβλήματα. Το πρώτο στάδιο είναι καθαρή γεωμετρία: μια ιδιωτική μέθοδος 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 κυλούν τα δεδομένα από κάτω, και αυτή είναι η έκταση της παγοποίησης που κάνει μόνο του αυτό το στοιχείο ελέγχου: δεν είναι η δυνατότητα Freeze Panes του Excel, και δεν υπάρχει ενσωματωμένος τρόπος να καρφιτσώσετε μια αυθαίρετη γραμμή ή στήλη δεδομένων ενώ το υπόλοιπο του φύλλου κυλά πέρα από αυτήν. Ένα όριο που αξίζει να δοκιμαστεί πριν διανείμετε έναν viewer πάνω σε αρχεία που δεν ελέγχετε πλήρως είναι ότι τα TopRow και LeftCol δεν περιορίζονται έναντι του πραγματικού χρησιμοποιούμενου εύρους του φύλλου εργασίας, οπότε ένα κουμπάκι συρμένο στο δομικό του όριο μπορεί να προσγειωθεί στη γραμμή 1.048.576 ή τη στήλη 16.384 και να δείξει ένα κενό πλέγμα αντί για την τελευταία γραμμή ή στήλη που πράγματι κρατά δεδομένα· βιβλία εργασίας αρκετά μεγάλα ώστε να το κάνουν αυτό αισθητό είναι συνήθως επίσης αρκετά μεγάλα ώστε να χρειάζονται την προσοχή στην πλευρά φόρτωσης που καλύπτεται στο άρθρο απόδοσης μεγάλων βιβλίων εργασίας

Καλωδίωση σχολίων και υπερσυνδέσμων σε συμβάντα ποντικιού και επιλογής

Το TXLSWorkbookViewer αντιμετωπίζει τα σχόλια και τους υπερσυνδέσμους ως χαρακτηριστικά όποιου κελιού είναι επιλεγμένο αυτή τη στιγμή παρά ως στόχους αιώρησης, οπότε τα SelectedCellCommentText, SelectedCellCommentAuthor, και SelectedCellHyperlink ενημερώνονται κάθε φορά που πυροδοτείται το OnSelectionChange, είτε η επιλογή μετακινήθηκε με κλικ ποντικιού, πλήκτρο βέλους, ή κλήση στο GoToCell. Ένα σχολιασμένο κελί παίρνει ένα μικρό κόκκινο τρίγωνο ζωγραφισμένο στην επάνω-δεξιά γωνία του ως οπτικό στοιχείο, παρόμοιο με τη δική του σημαία σχολίου του Excel, αλλά εκείνος ο δείκτης είναι καθαρά οπτικός· δεν υπάρχει ενσωματωμένο tooltip πυροδοτημένο από αιώρηση στο στοιχείο ελέγχου, οπότε μια εφαρμογή που θέλει ένα αναδυόμενο παράθυρο σε πέρασμα ποντικιού αντί σε επιλογή πρέπει να χτίσει εκείνο το επίπεδο η ίδια. Η ενεργοποίηση υπερσυνδέσμου λειτουργεί με τον ίδιο τρόπο επιλογή-πρώτα: το διπλό κλικ σε ένα κελί καλεί το ActivateSelectedCell, το οποίο διαβάζει το SelectedCellHyperlink και, αν δεν είναι κενό, εγείρει το OnHyperlinkClick με τη διεύθυνση στόχο και μια παράμετρο var Handled: Boolean για να την ορίσει ο χειριστής

Αυτό που δεν κάνει το OnHyperlinkClick είναι εξίσου σημαντικό: το TXLSWorkbookViewer ποτέ δεν καλεί το ShellExecute ούτε ανοίγει ένα πρόγραμμα περιήγησης μόνο του, ανεξάρτητα από το αν ο χειριστής ορίσει το Handled σε true ή το αφήσει false. Η πλοήγηση, και οποιαδήποτε απόφαση για το τι μετράει ως ασφαλής στόχος, είναι εξ ολοκλήρου ευθύνη της εφαρμογής host, κάτι που είναι η σωστή προεπιλογή για ένα εξάρτημα που δεν έχει ιδέα αν είναι ενσωματωμένο σε ένα έμπιστο εσωτερικό εργαλείο ή έναν viewer για αρχεία που μόλις μεταφόρτωσε ένας πελάτης

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 που χειρίζεται την κανονικοποίηση συγχώνευσης, και διαφέρουν ανά μηχανή σκόπιμα: ένας viewer δεμένος σε ένα κλασικό TXLSWorkbook περιορίζεται στη στήλη 256, το δομικό ανώτατο όριο της μορφής BIFF8, ενώ ένας δεμένος σε TXLSXWorkbook τιμά το σύγχρονο όριο 16.384 στηλών που κληρονόμησε το XLSX από το Excel 2007 και μετά. Οι γραμμές περιορίζονται στις 1.048.576 και στις δύο περιπτώσεις, οπότε η πρακτική διαφορά μεταξύ ανοίγματος ενός παλαιού αρχείου XLS και ενός αρχείου XLSX στον ίδιο viewer είναι εξ ολοκλήρου σχετικά με το πόσο δεξιά είναι διατεθειμένο να σας αφήσει να πάτε το πλέγμα

Τίποτα από αυτά δεν είναι εξωτικό μόλις αναλυθεί σε αναζήτηση pixel, κανονικοποίηση άγκυρας, και μια χούφτα χειριστές μηνυμάτων, αλλά το να κάνετε τα τρία να συμφωνήσουν κάτω από πραγματικά αρχεία, με πραγματικές συγχωνεύσεις, σχόλια, και υπερσυνδέσμους, είναι το μεγαλύτερο μέρος της δουλειάς σε ένα εξάρτημα σαν αυτό. Το TXLSWorkbookViewer διατίθεται ως μέρος του τυπικού εξαρτήματος Excel HotXLS για Delphi και C++Builder, μαζί με τα κλασικά και XLSX μοντέλα αντικειμένων από τα οποία αποδίδει