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

Διαγράμματα, εικόνες και αντικείμενα σχεδίασης HotXLS στο Delphi

Οτιδήποτε αιωρείται πάνω από το πλέγμα ενός φύλλου εργασίας (ένα γράφημα, ένα λογότυπο, μια σφραγίδα, ένα πλαίσιο επεξήγησης) είναι ένα αντικείμενο σχεδίασης, και ένα αντικείμενο σχεδίασης ορίζεται από δύο πράγματα: τι είναι, και πού είναι αγκυρωμένο. Η άγκυρα είναι το κομμάτι που ο κόσμος κάνει λάθος. Ένα γράφημα δεν ζει μέσα σε ένα κελί· κάθεται σε ένα ορθογώνιο καρφωμένο σε ένα εύρος γραμμών και στηλών, και τα δεδομένα που αποτυπώνει είναι ένα ξεχωριστό σύνολο αναφορών A1 για τις οποίες η άγκυρα δεν ξέρει τίποτα. Μετακινήστε το πλαίσιο και το γράφημα παραμένει στη θέση του. Εισάγετε γραμμές από κάτω του και το πλαίσιο γλιστράει προς τα κάτω μαζί τους. Το να κρατάτε αυτά τα δύο συστήματα συντεταγμένων ξεχωριστά είναι το μεγαλύτερο μέρος αυτού που κάνει τον κώδικα σχεδίασης να συμπεριφέρεται σωστά

Το HotXLS είναι μια εγγενής βιβλιοθήκη Object Pascal που διαβάζει και γράφει XLS και XLSX χωρίς αυτοματισμό Excel, και κουβαλά δύο ξεχωριστά μοντέλα σχεδίασης επειδή οι δύο μορφές αρχείων αποθηκεύουν σχέδια διαφορετικά. Η μορφή BIFF8 .xls κρατά τα γραφήματα σε δικά τους αποκλειστικά φύλλα και τα αιωρούμενα σχήματα σε ένα stream OfficeArt συνδεδεμένο στο φύλλο εργασίας. Η μορφή OOXML .xlsx μπορεί να ενσωματώσει ένα γράφημα μέσα στο πλέγμα, αγκυρωμένο σε ένα ορθογώνιο κελιών, δίπλα στο ίδιο είδος αιωρούμενων εικόνων και σχημάτων. Το μοντέλο αντικειμένων αντικατοπτρίζει αυτόν τον διαχωρισμό, και οι αποτυχίες που αξίζει να γραφτούν προέρχονται όλες από την εφαρμογή των κανόνων της μίας μορφής στην άλλη

Ποιος περιέκτης μπορεί να κρατήσει τι

Η επιλογή περιέκτη πρέπει να προηγηθεί κάθε κώδικα γραφήματος, επειδή οι διαθέσιμοι τύποι αντικειμένων διαφέρουν ανάμεσα στις δύο μορφές:

Διάγραμμα που συγκρίνει δοχεία σχεδίων στο HotXLS από Delphi: φύλλα γραφημάτων και σχήματα OfficeArt σε παλιό XLS έναντι ενσωματωμένων γραφημάτων, εικόνων και πλαισίων κειμένου σε XLSX
Οι δύο μορφές αρχείου εκθέτουν διαφορετικά API σχεδίασης, οπότε ο περιέκτης πρέπει να διαλεχθεί πριν γραφτεί οποιοσδήποτε κώδικας γραφήματος
  • XLS (BIFF8): τα γραφήματα ζουν σε αποκλειστικά φύλλα γραφημάτων που δημιουργούνται μέσω του AddChartSheet στη συλλογή Sheets. Οι εικόνες, τα text box, τα ορθογώνια, τα οβάλ, και οι γραμμές είναι σχήματα OfficeArt που διαχειρίζονται μέσω της συλλογής Shapes του φύλλου εργασίας. Δεν υπάρχει API για την ενσωμάτωση γραφήματος μέσα σε ένα κανονικό πλέγμα φύλλου εργασίας
  • XLSX (OOXML): τα γραφήματα μπορούν να ενσωματωθούν απευθείας σε ένα φύλλο εργασίας με το TXLSXWorksheet.AddChart, αγκυρωμένα σε ένα ορθογώνιο κελιών, ή να τοποθετηθούν σε αποκλειστικό φύλλο γραφήματος με το TXLSXWorkbook.AddChartSheet. Οι εικόνες μπαίνουν με το AddImage ή το AddImageFromFile, και οι αιωρούμενες ετικέτες με το AddTextBox

Άρα μια απαίτηση διατυπωμένη ως "ένα φύλλο dashboard με το γράφημα δίπλα στους αριθμούς" είναι στην πραγματικότητα μια απαίτηση για .xlsx. Μπορείτε να την προσεγγίσετε στο .xls μόνο σπρώχνοντας το γράφημα στο δικό του φύλλο, κάτι που αλλάζει το πώς πλοηγείται ο χρήστης στο αρχείο και αλλάζει το πώς πρέπει να συμπεριφέρεται ο κώδικάς σας. Το φύλλο που επιστρέφει το AddChartSheet στην πλευρά XLS είναι ένα υπο-stream γραφήματος, όχι ένα πλέγμα: η εγγραφή σε αυτό με το Cells.Item παράγει ένα ασυνεπές stream σχεδίασης που δημιουργείται χωρίς σφάλμα και το οποίο το Excel έπειτα απορρίπτει κατά το άνοιγμα. Το γράφημα απλώς εξαφανίζεται, και τίποτα στο build log δεν λέει γιατί. Αντιμετωπίστε το επιστρεφόμενο φύλλο ως μόνο-για-γράφημα και ολόκληρη η κατηγορία αναφορών "λείπει το γράφημα" εξαφανίζεται

Ενσωμάτωση γραφήματος σε φύλλο εργασίας XLSX

Η διαδρομή XLSX είναι αυτή με περιθώριο ελιγμών, και είναι εκεί όπου τα δύο συστήματα συντεταγμένων από την αρχή γίνονται συγκεκριμένα. Το ορθογώνιο άγκυρας που περνά στο AddChart εκφράζεται σε γραμμές και στήλες φύλλου εργασίας και καθορίζει πού κάθεται το πλαίσιο του γραφήματος. Τα δεδομένα σειράς εκφράζονται ως απόλυτες αναφορές A1 που περιλαμβάνουν το όνομα φύλλου. Είναι ανεξάρτητα: μπορείτε να μετακινήσετε το πλαίσιο στην απέναντι άκρη του φύλλου και θα εξακολουθεί να απεικονίζει τα ίδια κελιά

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Chart: TXLSXChart;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Sales');
    Sheet.Cells[1, 1].Value := 'Region';
    Sheet.Cells[1, 2].Value := 'Revenue';
    Sheet.Cells[2, 1].Value := 'East';
    Sheet.Cells[2, 2].Value := 1184350;
    Sheet.Cells[3, 1].Value := 'Central';
    Sheet.Cells[3, 2].Value := 902210;
    Sheet.Cells[4, 1].Value := 'West';
    Sheet.Cells[4, 2].Value := 1010675;

    // Πλαίσιο αγκυρωμένο στις γραμμές 6..22, στήλες 1..8
    Chart := Sheet.AddChart(xlsxChartColumn, 'Revenue by Region', 6, 1, 22, 8);
    Chart.AddSeries('Revenue', 'Sales!$A$2:$A$4', 'Sales!$B$2:$B$4');
    Chart.ValueAxisTitle := 'USD';

    Sheet.AddImageFromFile(1, 5, 'logo.png');
    Book.SaveAs('dashboard.xlsx');
  finally
    Book.Free;
  end;
end;

Το όρισμα που δαγκώνει είναι η συμβολοσειρά εύρους που δίνεται στο AddSeries. Είναι κυριολεκτική, καταγεγραμμένη τη στιγμή της κλήσης, και δεν έχει ιδέα ότι μπορεί να προσθέσετε είκοσι ακόμη γραμμές δεδομένων αργότερα. Χτίστε την από έναν αριθμό γραμμών που υπολογίσατε αφού γράφτηκαν τα δεδομένα, ποτέ πριν. Τα γραφήματα scatter και bubble υπερφορτώνουν τα ίδια δύο ορίσματα με διαφορετικές σημασίες: το εύρος κατηγοριών τώρα παρέχει τις τιμές X και το εύρος τιμών παρέχει το Y, και η ακτίνα bubble προέρχεται από μια τρίτη αναφορά που ορίζεται μέσω του BubbleSizeRange στο επιστρεφόμενο TXLSXChartSeries. Διαβάστε την κλήση ως "X, Y, μέγεθος" αντί για "κατηγορίες, τιμές" μόλις φύγετε από την οικογένεια στηλών-και-ράβδων

Το TXLSXChartType καλύπτει γραφήματα στήλης, ράβδου, γραμμής, πίτας, περιοχής, δακτυλίου, scatter, bubble, και radar, κάτι που καλύπτει το καθημερινό ρεπερτόριο αναφορών. Για ένα γράφημα πλήρους σελίδας χωρίς περιβάλλον πλέγμα, το Book.AddChartSheet επιστρέφει ένα φύλλο του οποίου η ιδιότητα IsChartSheet είναι true. Είναι το αντίστοιχο .xlsx του παλαιού φύλλου γραφήματος και φέρει την ίδια προσδοκία: μην γράφετε περιεχόμενο κελιού σε αυτό

Οι εικόνες μπαίνουν ως bytes, και έχουν μέγεθος σε EMU

Υπάρχουν δύο υπερφορτώσεις για την εισαγωγή εικόνας, και το να τις μπερδέψετε είναι το σφάλμα εικόνας που εμφανίζεται περισσότερο στο code review. Το AddImage(ARow, ACol, AData, AFormat) θέλει τα ήδη κωδικοποιημένα bytes εικόνας στο AData: το ακατέργαστο περιεχόμενο ενός PNG, JPEG, GIF, ή BMP. Δώστε του μια διαδρομή αρχείου και έχετε αποθηκεύσει μια συμβολοσειρά σαράντα bytes που κανένα πρόγραμμα προβολής δεν μπορεί να αποκωδικοποιήσει, που είναι ακριβώς η αναφορά σπασμένου εικονιδίου εικόνας που δεν θέλετε να κάνετε debug μετά την ανάπτυξη. Όταν η πηγή είναι ένα αρχείο στον δίσκο, καλέστε αντ' αυτού το AddImageFromFile και αφήστε τη βιβλιοθήκη να διαβάσει τα bytes και να ταξινομήσει τη μορφή για εσάς

Έπειτα έρχεται η διαστασιολόγηση. Το DrawingML δεν μετρά σε pixel· μετρά σε English Metric Units, όπου 914400 EMU κάνουν μία ίντσα και, στα 96 DPI, 9525 EMU κάνουν ένα pixel. Το αντικείμενο TXLSXImage εκθέτει τα WidthEMU και HeightEMU, οπότε ένα λογότυπο που πρέπει να αποδοθεί σε 180 επί 60 pixel χρειάζεται 1714500 επί 571500 EMU. Βάλτε αυτή τη μετατροπή σε μια επώνυμη σταθερά και υπολογίστε πάνω σε αυτήν. Μαγικοί αριθμοί όπως το 1714500 σκορπισμένοι μέσα στον κώδικα είναι δυσανάγνωστοι και γίνονται σιωπηλά λάθος την πρώτη φορά που κάποιος αλλάζει το DPI στόχου. Η γραμμή και η στήλη άγκυρας, παρεμπιπτόντως, ξεκινούν από το ένα, ταιριάζοντας με το υπόλοιπο API κελιών αντί με τα μαθηματικά EMU που ξεκινούν από το μηδέν

Διάγραμμα των δύο συστημάτων συντεταγμένων πίσω από το TXLSXWorksheet.AddChart στο HotXLS: το πλαίσιο γραφήματος αγκυρωμένο σε γραμμές και στήλες φύλλου εργασίας ενώ τα δεδομένα σειρών του χρησιμοποιούν απόλυτες αναφορές A1
Το πλαίσιο είναι καρφωμένο σε γραμμές και στήλες ενώ η πλοκή διαβάζει απόλυτες αναφορές A1, και το ένα σύστημα συντεταγμένων δεν ξέρει για το άλλο

Φύλλα γραφημάτων και σχήματα σε παλαιά αρχεία XLS

Στην πλευρά BIFF8 η πλουσιότερη υπερφόρτωση του AddChartSheet δέχεται τον τύπο γραφήματος, τους τίτλους αξόνων, και έναν ανοιχτό πίνακα από εγγραφές TXLSChartSeriesInfo, όπου κάθε εγγραφή κρατά ένα όνομα και ένα εύρος κατηγοριών και τιμών ως συμβολοσειρές. Τα αιωρούμενα σχήματα είναι ξεχωριστό θέμα: πηγαίνουν στο ίδιο το φύλλο εργασίας δεδομένων, μέσω της συλλογής Shapes του, όχι στο φύλλο γραφήματος

var
  Book: IXLSWorkbook;
  Data, Trend: IXLSWorksheet;
  Series: array[0..0] of TXLSChartSeriesInfo;
begin
  Book := TXLSWorkbook.Create;   // μετρημένο μέσω interface: μην κάνετε Free
  Data := Book.Sheets.Add;
  Data.Name := 'Data';
  Data.Cells.Item[1, 1].Value := 'Month';
  Data.Cells.Item[1, 2].Value := 'Units';
  Data.Cells.Item[2, 1].Value := 'Apr';
  Data.Cells.Item[2, 2].Value := 1530;
  Data.Cells.Item[3, 1].Value := 'May';
  Data.Cells.Item[3, 2].Value := 1721;

  Series[0].Name := 'Units';
  Series[0].Categories := 'Data!$A$2:$A$3';
  Series[0].Values := 'Data!$B$2:$B$3';
  Trend := Book.Sheets.AddChartSheet('Trend', xlsChartTypeLine,
    'Units sold', 'Month', 'Units', Series);
  // Το Trend είναι υπο-stream γραφήματος: ποτέ μην καλείτε μεθόδους κελιού σε αυτό

  Data.Shapes.AddTextBox('Source: ERP nightly export', 6, 1, 8, 4);
  Data.Shapes.AddPicture('approved-stamp.bmp');
  Book.SaveAs('trend.xls');
end;

Δύο λεπτομέρειες κύκλου ζωής έχουν σημασία εδώ, και τραβούν προς αντίθετες κατευθύνσεις. Το TXLSWorkbook κρατιέται μέσω του interface IXLSWorkbook και είναι reference-counted, οπότε το να καλέσετε Free σε αυτό εσείς οι ίδιοι πυροδοτεί διπλή αποδέσμευση. Το TXLSXWorkbook από τις προηγούμενες ενότητες είναι ένα απλό αντικείμενο και πρέπει να απελευθερώνεται μέσα σε ένα try..finally. Ο ίδιος reviewer κώδικα που επισημαίνει ένα απόν Free στην πλευρά XLSX πρέπει να επισημάνει ένα παρόν στην πλευρά XLS, κάτι που είναι πραγματικός κίνδυνος παραπατήματος όταν δουλεύετε και στις δύο μορφές μέσα στην ίδια unit. Οι ίδιοι οι βοηθοί σχημάτων είναι ομοιόμορφοι: AddRectangle, AddOval, και AddLine, με το DeleteInRange για να καθαρίσετε μια περιοχή σχεδίων, όλα αγκυρώνονται με ζεύγη γραμμής και στήλης, οπότε ένα template που εισάγει γραμμές από πάνω τους τα μετακινεί μαζί με το πλέγμα

Μία ακόμη ιδιότητα δικαιολογεί την ύπαρξή της σε παλαιά αρχεία. Το TXLSPicture.TransparentColor κρύβει ένα επιλεγμένο χρώμα φόντου από ένα bitmap, που είναι πώς τοποθετείτε μια μη ορθογώνια σφραγίδα (μια σφραγίδα "Approved", ένα watermark) πάνω από το πλέγμα σε μια μορφή της οποίας η απόδοση BIFF ποτέ δεν έμαθε το PNG alpha. Ορίστε το χρώμα πάνω στο οποίο δημιουργήθηκε η σφραγίδα και το γύρω ορθογώνιο εξαφανίζεται

Τα χρώματα θέματος δεν επιβιώνουν από μια μετάβαση BIFF8

Τα γεμίσματα σχεδίασης OOXML μπορούν να δείχνουν σε μια θέση χρώματος θέματος, γι' αυτό το ξαναχρωμάτισμα ενός ολόκληρου .xlsx αλλάζοντας το θέμα του είναι φθηνό. Οι εγγραφές σχεδίασης BIFF8 δεν έχουν τέτοια θέση. Όταν το HotXLS εφαρμόζει ένα χρώμα θέματος σε ένα σχέδιο XLS, αναλύει το χρώμα σε μια κυριολεκτική τιμή RGB και την αποθηκεύει· ο δείκτης θέματος από τον οποίο προήλθε χάνεται τη στιγμή που γράφεται το αρχείο, και το ξανάνοιγμα δεν μπορεί να τον ανακτήσει. Αυτό πιάνει ιδιαίτερα τα εργαλεία αναφορών white-label, το είδος που ξαναεπωνυμοποιεί το ίδιο παραγόμενο έγγραφο για πολλούς πελάτες. Κρατήστε την αντιστοίχιση θέματος-σε-RGB στη δική σας διαμόρφωση και επανεφαρμόστε τη κάθε φορά που παράγετε, αντί να περιμένετε να τη διαβάσετε πίσω από ένα αποθηκευμένο .xls

Διάγραμμα εισαγωγής εικόνων HotXLS από Delphi: το AddImage θέλει κωδικοποιημένα bytes ενώ το AddImageFromFile διαβάζει το αρχείο, και pixels στα 96 DPI μετατρέπονται σε τιμές WidthEMU και HeightEMU
Τα byte εικόνας και οι διαδρομές αρχείων ανήκουν σε διαφορετικά overloads, και τα μεγέθη pixel στην οθόνη μετατρέπονται σε EMUs πριν φτάσουν στο αντικείμενο εικόνας

Μια σχετική απόφαση εμφανίζεται στην πλευρά της απόδοσης. Στην πρόσοψη XLS μπορεί να ζητηθεί να παραλείψει εντελώς την ανάλυση του επιπέδου σχεδίασης όταν το μόνο που θέλετε από ένα μεγάλο παλαιό αρχείο είναι τα δεδομένα κελιών του, ορίζοντας το _DisableGraphics σε true, και αυτό αφαιρεί πραγματικό χρόνο από μαζικές αναγνώσεις. Η παγίδα είναι μόνιμη: ένα βιβλίο εργασίας που ανοίχτηκε έτσι δεν έχει stream OfficeArt στη μνήμη, οπότε η αποθήκευσή του γράφει τα σχέδια εκτός ύπαρξης. Κρατήστε τη σημαία για εργασίες αναλυτικής μόνο ανάγνωσης. Η ευρύτερη εικόνα απόδοσης βρίσκεται στις σημειώσεις μας για την απόδοση μεγάλων βιβλίων εργασίας στο HotXLS

Διατήρηση σταθερών αγκυρών ενώ το πλέγμα αλλάζει

Οι αναφορές σπάνια παραμένουν στο μέγεθος στο οποίο παράχθηκαν, και εδώ είναι όπου αποδίδει το μοντέλο άγκυρας από την αρχή. Οι δομικές λειτουργίες της πρόσοψης XLSX (InsertRows, DeleteRows, και τα αντίστοιχα για στήλες) μετακινούν τα εξαρτημένα επίπεδα μαζί με τα κελιά. Συγχωνευμένες περιοχές, υπερσύνδεσμοι, σχόλια, παγωμένα παράθυρα, εύρη φίλτρων, μορφές υπό όρους, επικυρώσεις, πίνακες, καθορισμένα ονόματα, και, για αυτό το θέμα, οι άγκυρες εικόνων και γραφημάτων ταξιδεύουν όλες μαζί. Ένα λογότυπο αγκυρωμένο στη γραμμή 1 παραμένει στην κορυφή όταν δέκα γραμμές μπαίνουν από κάτω του. Ένα πλαίσιο γραφήματος αγκυρωμένο κάτω από το μπλοκ δεδομένων γλιστράει προς τα κάτω καθώς το μπλοκ μεγαλώνει. Το ένα πράγμα που δεν ξαναγράφεται είναι οποιαδήποτε συμβολοσειρά εύρους καταγράψατε ως κυριολεκτική πριν συμβεί η εισαγωγή, καθώς είναι απλά κείμενο που η βιβλιοθήκη δεν έχει λόγο να επανεξετάσει. Αυτό καθορίζει την ασφαλή σειρά για τη συμπλήρωση ενός template: γράψτε και αναδιαμορφώστε πρώτα τα δεδομένα, και δημιουργήστε γραφήματα και τοποθετήστε εικόνες ως το τελικό πέρασμα, με κάθε συμβολοσειρά εύρους παραγόμενη από τους αριθμούς γραμμών που έχετε μετά τις εισαγωγές, όχι πριν

Δύο μικρότερα εργαλεία ολοκληρώνουν το κιτ τοποθέτησης. Το TXLSTextBox.SetArea στην πλευρά XLS επαναγκυρώνει ένα υπάρχον text box ή auto shape σε ένα νέο ορθογώνιο κελιών, κάτι που νικά τη διαγραφή και αναδημιουργία του όταν μετατοπίζεται ένα μπλοκ υποσέλιδου. Και η υπερφόρτωση bitmap του AddPicture δέχεται ένα ζωντανό TBitmap με προαιρετική σημαία διαφάνειας, οπότε οτιδήποτε μπορεί να σχεδιάσει ο δικός σας κώδικας VCL (ένα μετρητή, μια λωρίδα sparkline, έναν τύπο γραφήματος που δεν προσφέρει η εγγενής λίστα) μπορεί να σφραγιστεί κατευθείαν μέσα στο φύλλο χωρίς να γραφτεί πρώτα ένα προσωρινό αρχείο

Τα γραφήματα και οι εικόνες είναι σχεδόν πάντα το τελικό επίπεδο πάνω σε μια ήδη δομημένη αναφορά, γι' αυτό η προπαρασκευαστική εργασία καθορίζει αν θα προσγειωθούν καθαρά. Η συμπλήρωση των δεδομένων που θα αναφέρει ένα γράφημα καλύπτεται στη δημιουργία αναφορών βάσει template, και η διατήρηση σταθερού του πλέγματος κάτω από τις άγκυρές σας είναι το θέμα των συγχωνευμένων κελιών και ελέγχου διάταξης. Η πλήρης τεκμηρίωση κλάσεων και μεθόδων βρίσκεται στη σελίδα προϊόντος HotXLS Delphi Component