Διατρέξτε τα κελιά ενός μόλις ανοιγμένου template αναφοράς και ένας συγχωνευμένος τίτλος συμπεριφέρεται σαν διαρροή. Διαβάζετε το A1 και παίρνετε «Quarterly Statement»· διαβάζετε τα B1 έως F1, που κάθονται ορατά κάτω από το ίδιο banner, και δεν παίρνετε τίποτα. Γράφετε μια τιμή στο C1 για να διορθώσετε την κεφαλίδα και δεν εμφανίζεται ποτέ στην οθόνη. Το πλέγμα δεν έχασε τα δεδομένα σας. Κάνει ακριβώς αυτό που σημαίνει μια συγχώνευση: και στο XLS και στο XLSX, ένα συγχωνευμένο ορθογώνιο αποδίδει το περιεχόμενο ενός κελιού, την επάνω-αριστερά άγκυρα, και αντιμετωπίζει τα υπόλοιπα ως καλυμμένο χώρο που κρατά τιμές αλλά δεν τις δείχνει ποτέ. Οι χρήστες του Excel το αφομοιώνουν μέσα από δοκιμή και λάθος. Μια γεννήτρια αναφορών πρέπει να το κωδικοποιήσει ως κανόνα, επειδή σε δημιουργημένο κώδικα το σύμπτωμα είναι μια κενή περιοχή χωρίς καμία εξαίρεση να την εντοπίσει. Το HotXLS, μια εγγενής βιβλιοθήκη Object Pascal που διαβάζει και γράφει και τις δύο μορφές Excel από τη Delphi και τη C++Builder, αναδεικνύει τον πίνακα συγχωνεύσεων αρκετά ρητά ώστε να μπορείτε να προγραμματίζετε σύμφωνα με τον κανόνα αντί να τον ξανανακαλύψετε σε ένα ticket υποστήριξης
Μία τιμή, μία άγκυρα
Μια συγχώνευση είναι μια οδηγία εμφάνισης τοποθετημένη πάνω σε ένα πλέγμα που δεν αλλάζει σχήμα. Κάθε καλυμμένο κελί εξακολουθεί να υπάρχει στο αρχείο ως δική του θέση· η εγγραφή συγχώνευσης απλώς λέει σε έναν καταναλωτή να ζωγραφίσει το περιεχόμενο της άγκυρας σε όλο το ορθογώνιο. Αυτή η διάκριση οδηγεί τρεις συμπεριφορές που αξίζει να αφομοιώσετε πριν γράψετε οποιονδήποτε κώδικα διάταξης. Η ανάγνωση ενός καλυμμένου κελιού επιστρέφει τη δική του αποθηκευμένη τιμή, που για ένα banner που χτίσατε είναι συνήθως κενή, οπότε οποιοσδήποτε κώδικας επιθεωρεί έναν συγχωνευμένο τίτλο πρέπει να επιλύσει και να διαβάσει την άγκυρα. Η εγγραφή σε ένα καλυμμένο κελί πετυχαίνει στο επίπεδο αρχείου και δεν εμφανίζεται πουθενά, που είναι η παγίδα της αόρατης κεφαλίδας από την εισαγωγή. Και η κατάργηση συγχώνευσης μιας περιοχής αποκαλύπτει ό,τι βρισκόταν από κάτω όλον αυτόν τον καιρό, οπότε μια περιπλανώμενη τιμή γραμμένη σε καλυμμένο χώρο μετατρέπεται σε ορατό ελάττωμα την ημέρα που κάποιος διαλύει τη συγχώνευση
Στην πλευρά XLSX αυτός ο πίνακας είναι ένα πλήρες αντικείμενο. Η Sheet.MergedCells κουβαλά τις Add('A1:C1'), FindAt(Row, Col), DeleteAt και Items, και η κλήση που χρησιμοποιείτε περισσότερο είναι η FindAt: δώστε της οποιαδήποτε συντεταγμένη και επιστρέφει τη συγχωνευμένη περιοχή που καλύπτει εκείνο το κελί, ή nil όταν το κελί στέκεται μόνο του. Αυτή η μοναδική αναζήτηση είναι το θεμέλιο και για τα δύο μισά του σωστού χειρισμού συγχωνεύσεων, την ασφαλή ανάγνωση και τον φύλακα εγγραφής, και τα δύο εμφανίζονται παρακάτω
Δύο προσόψεις, δύο ιδιώματα συγχώνευσης
Το HotXLS κρατά την κλασική μηχανή BIFF8 .xls και τη μηχανή OOXML .xlsx ως ξεχωριστά μοντέλα αντικειμένων, και συλλαβίζουν τη συγχώνευση διαφορετικά επειδή κατάγονται από διαφορετικές συμβάσεις. Η πρόσοψη XLS ακολουθεί το ιδίωμα COM του Excel: παίρνετε ένα εύρος από μια ιδιότητα με δείκτη δύο ορισμάτων και καλείτε την Merge με ένα OleVariant του οποίου η τιμή αποφασίζει τη γεωμετρία που θα καταλήξετε να έχετε
var
Book: IXLSWorkbook; // μετρημένο ως διεπαφή: χωρίς χειροκίνητο Free
Sh: IXLSWorksheet;
begin
Book := TXLSWorkbook.Create;
Sh := Book.Sheets[1]; // η συλλογή φύλλων XLS ξεκινά από το ένα
Sh.Range['A1', 'F1'].Merge(False); // False = ένα συγχωνευμένο μπλοκ
Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
Sh.Range['A3', 'F4'].Merge(True); // True = merge across: μία συγχώνευση ανά γραμμή
Book.SaveAs('layout.xls');
end;
Το όρισμα της Merge είναι το σημείο που ο κόσμος κάνει λάθος. Πάνω σε ένα εύρος δύο γραμμών, η Merge(True) παράγει δύο ανεξάρτητες συγχωνεύσεις μίας γραμμής, που είναι το «Merge Across» του Excel και ακριβώς αυτό που θέλετε για μια στοιβαγμένη ζώνη κεφαλίδας που πρέπει να κρατά τις γραμμές της διαχωρίσιμες. Η Merge(False) συντήκει ολόκληρο το ορθογώνιο σε ένα ενιαίο μπλοκ. Το εύρος επίσης αναφέρει την MergeCells ως σημαία κατάστασης, επιστρέφει την περιοχή που το περιέχει μέσω της MergeArea, και διαλύεται με την Unmerge. Η πρόσοψη XLSX εκθέτει τις ίδιες λειτουργίες με διαφορετικά ονόματα: η Sheet.MergeCells(Row1, Col1, Row2, Col2) δέχεται ακέραια όρια, η TXLSXRange.Merge δέχεται την ισοδύναμη παραλλαγή Across, και η συλλογή MergedCells κρατά το αποτέλεσμα
Ένα template που μεγαλώνει μαζί με τα δεδομένα του
Ένα πραγματικό template αναφοράς δεν είναι ένα σταθερό πλέγμα. Η κεφαλίδα και τα σύνολα είναι σταθερά, αλλά η ενότητα λεπτομερειών ανάμεσά τους τεντώνεται σε ό,τι επιστρέφει το ερώτημα. Το μοτίβο που αντέχει κρατά μία πλήρως στιλαρισμένη γραμμή λεπτομέρειας στο template, την κλωνοποιεί μία φορά ανά εγγραφή, και έπειτα ανοίγει ένα κενό μπροστά από το μπλοκ συνόλων ώστε ό,τι είναι αγκυρωμένο από κάτω να γλιστρήσει προς τα κάτω χωρίς να χάσει τη μορφοποίησή του
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611'; // η τιμή πηγαίνει στην άγκυρα, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1; // δείκτης pool ξεκινά από το μηδέν, πλευρά κελιού ξεκινά από το ένα
// η γραμμή 5 είναι η στιλαρισμένη γραμμή template λεπτομέρειας
for I := 0 to ItemCount - 1 do
Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1); // στιλ και τύποι ταξιδεύουν μαζί της
// άνοιξε ένα κενό πάνω από το μπλοκ συνόλων· το περιεχόμενο από κάτω μετατοπίζεται προς τα κάτω
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);
Δύο γραμμές αξίζουν μια δεύτερη ματιά. Η ανάθεση γραμματοσειράς κουβαλά ένα off-by-one που δαγκώνει σιωπηλά: η Fonts.Add επιστρέφει μια θέση pool που ξεκινά από το μηδέν, ενώ ένα κελί αποθηκεύει μια αναφορά γραμματοσειράς που ξεκινά από το ένα όπου το 0 σημαίνει την προεπιλεγμένη γραμματοσειρά, οπότε η παράλειψη του + 1 δεν σηκώνει τίποτα, απλώς στιλάρει τον τίτλο σας στη λάθος γραμματοσειρά. Η άλλη γραμμή είναι η CopyRange, που μετακινεί μορφοποίηση και τύπους μαζί με τιμές. Αυτός είναι όλος ο λόγος να κλωνοποιείτε μια χειροφτιαγμένη γραμμή template αντί να ανακατασκευάζετε την εμφάνισή της σε κώδικα. Ένας σχεδιαστής κατέχει την εμφάνιση μία φορά, στο template· η γεννήτρια απλώς χύνει δεδομένα μέσα σε αντίγραφά του
Αυτός ο διαχωρισμός κλιμακώνεται περαιτέρω όταν η επαναχρησιμοποιήσιμη διάταξη ζει στο δικό της βιβλίο εργασίας, ας πούμε ένα φύλλο με ζώνες κεφαλίδας και υποσέλιδου κοινές σε πολλές αναφορές. Η CopyRangeTo εκτελεί τον ίδιο κλωνισμό πέρα από τα όρια φύλλων εργασίας, δεχόμενη ένα φύλλο στόχο συν συντεταγμένες προορισμού, οπότε μια γεννήτρια μπορεί να κρατά ένα άθικτο φύλλο template και να αποτυπώνει τις περιοχές του σε όσα φύλλα εξόδου χρειάζεται μια εργασία. Η εναλλακτική, η μεταβολή του template επί τόπου και η προσπάθεια αποκατάστασής του αργότερα, είναι το είδος πράγματος που λειτουργεί μέχρι την ημέρα που μια εκτέλεση ματαιώνεται στα μισά
Τι μετακινεί η InsertRows, και τι όχι
Το μοτίβο «μεγαλώνω ένα template» λειτουργεί μόνο επειδή η InsertRows του XLSX είναι μια δομική επεξεργασία και όχι ένα ανακάτεμα κελιών. Όταν ανοίγει ένα κενό, μετεγκαθιστά τις συγχωνευμένες περιοχές, τα ύψη γραμμών, τους υπερσυνδέσμους, τα σχόλια, τα παγωμένα παράθυρα, τα εύρη autofilter, τη μορφοποίηση υπό όρους, τις επικυρώσεις δεδομένων, τους πίνακες, τα defined names, τις άγκυρες εικόνων και τις άγκυρες γραφημάτων που βρίσκονται κάτω από το σημείο εισαγωγής, όχι μόνο τις τιμές κελιών. Αυτό είναι που επιτρέπει στο μπλοκ συνόλων να φτάσει στη νέα του γραμμή με τις συγχωνεύσεις και τις μορφές αριθμών άθικτες αντί να φτάσει γδυμένο
Τα δύο τεκμηριωμένα όριά της είναι αυτά γύρω από τα οποία πρέπει να σχεδιάσετε. Η προσαρμογή τύπων περιορίζεται στο φύλλο που επεξεργάζεται: οι αναφορές μέσα σε εκείνο το φύλλο ξαναγράφονται, και ένας τύπος σε άλλο φύλλο που δείχνει μέσα στη μετατοπισμένη περιοχή επίσης ξαναγράφεται, αλλά η προσαρμογή ακολουθεί μόνο αναφορές που στοχεύουν το επεξεργαζόμενο φύλλο, οπότε οποιοδήποτε σχήμα αναφοράς μεταξύ βιβλίων εργασίας αξίζει τον δικό του έλεγχο αντί για τυφλή εμπιστοσύνη. Το δεύτερο όριο είναι πιο αιχμηρό, και βρίσκεται στην πλευρά XLS. Οι pivot πίνακες επιβιώνουν σε κύκλους ανοίγματος-αποθήκευσης ως ακατέργαστες διατηρημένες εγγραφές, όχι ως μοντελοποιημένα αντικείμενα που μπορεί να μετακινήσει το HotXLS, οπότε η εισαγωγή γραμμών δεν μετεγκαθιστά το αποτύπωμα ενός pivot. Οποιοδήποτε template χτίζετε για τη μορφή .xls πρέπει να παρκάρει τις περιοχές pivot του καλά μακριά από οποιαδήποτε ζώνη που μεγαλώνει
Άρνηση εγγραφής δεδομένων μέσα σε χώρο διάταξης
Η αστοχία συγχωνευμένου κελιού που πράγματι φτάνει στην παραγωγή δεν είναι η κοσμητική. Είναι δομική: μια γραμμή λεπτομέρειας παρασύρεται μέσα σε μια συγχωνευμένη ζώνη διάταξης, οι τιμές της προσγειώνονται σε καλυμμένα κελιά και γίνονται αόρατες, και τα σύνολα στηλών σιωπηλά σταματούν να ταιριάζουν με ό,τι μπορεί να δει όποιος διαβάζει το φύλλο. Επειδή η FindAt απαντά στην ερώτηση της καλυπτικής περιοχής για οποιαδήποτε συντεταγμένη, η γεννήτρια μπορεί να αρνηθεί εκείνη την εγγραφή τη στιγμή που θα συνέβαινε αντί να παραδώσει μια αναφορά που σιωπηλά υπολογίζει λιγότερο
// άρνηση εγγραφής δεδομένων λεπτομέρειας μέσα σε μια συγχωνευμένη περιοχή διάταξης
if Sheet.MergedCells.FindAt(Row, 1) <> nil then
raise Exception.CreateFmt('row %d overlaps a merged layout region', [Row]);
Sheet.Cells[Row, 1].Value := Detail.Description;
Ο ίδιος έλεγχος ορίου ανήκει οπουδήποτε ένας χρήστης θα ταξινομήσει ή θα φιλτράρει αργότερα την έξοδο. Ένα εύρος με συγχωνεύσεις μέσα του δεν μπορεί να ταξινομηθεί καθαρά, επειδή η ταξινόμηση μετακινεί γραμμές ανεξάρτητα και μια συγχώνευση που εκτείνεται σε γραμμές δεν έχει μία μοναδική γραμμή για να ταξιδέψει μαζί της· το Excel απαντά με σφάλμα ή με μια ανακατεμένη διάταξη. Η πειθαρχία που κρατά τις αναφορές σωστές είναι γεωγραφική. Περιορίστε τις συγχωνεύσεις σε ζώνες τίτλου, διαχωριστές ενοτήτων και μπλοκ υπογραφής, και κρατήστε το πινακοποιημένο μεσαίο μέρος του φύλλου επίπεδο. Το άρθρο για δημιουργία αναφορών βάσει template αναπτύσσει αυτόν τον διαχωρισμό διάταξης-έναντι-δεδομένων σε μια πλήρη ροή εργασίας οδηγούμενη από placeholder, και το άρθρο για μορφοποίηση υπό όρους και rich text καλύπτει τη στιλάρισμα αυτής της επίπεδης ζώνης δεδομένων
Πώς υποβαθμίζονται οι συγχωνεύσεις στην έξοδο
Μια συγχώνευση είναι μια έννοια βιβλίου εργασίας, και κάθε μορφή εξαγωγής προσανατολισμένη σε κείμενο την τιμά σε διαφορετικό βαθμό. Το να γνωρίζετε τις τρεις συμπεριφορές εκ των προτέρων γλιτώνει έναν κύκλο QA. Η εξαγωγή HTML αναπαράγει τις συγχωνεύσεις πιστά, εκπέμποντας colspan και rowspan σε έναν ενιαίο πίνακα, οπότε μια αναφορά προορισμένη για browser κρατά την εμφάνισή της με ζώνες. Η εξαγωγή RTF δεν εκτείνεται καθόλου σε στήλες: το κείμενο άγκυρα προσγειώνεται στο δικό του κελί και το υπόλοιπο πλάτος της συγχώνευσης βγαίνει ως κενά κελιά, κάτι που αφήνει έναν πλατύ τίτλο οπτικά σπρωγμένο προς τα αριστερά σε έναν επεξεργαστή κειμένου. Το CSV δεν έχει καμία έννοια συγχώνευσης, οπότε η τιμή άγκυρα καταλαμβάνει ένα πεδίο και κάθε καλυμμένο κελί εκπέμπεται ως κενό πεδίο. Το συμπέρασμα για ένα βιβλίο εργασίας που τροφοδοτεί επίσης οριοθετημένες εξαγωγές είναι να κρατάτε οτιδήποτε φέρει φορτίο έξω από τη γεωμετρία συγχώνευσης· το άρθρο για εξαγωγή CSV, TSV και HTML περνά μέσα από κάθε μορφή λεπτομερώς
Μια καθησυχαστική σημείωση για όποιον το ζυγίζει έναντι του μεγέθους αρχείου: οι συγχωνεύσεις κοστίζουν σχεδόν τίποτα στην κλίμακα μιας αναφοράς. Ο πίνακας συγχωνεύσεων είναι μικροσκοπικός δίπλα στα δεδομένα κελιών, και η ανάγνωση ενός καλυμμένου κελιού περνά ακόμη μέσα από την FindAt αντί για σάρωση. Η πίεση απόδοσης σε μεγάλα βιβλία εργασίας προέρχεται αλλού, κυρίως από την ανάπτυξη pool στιλ και τη μνήμη που κρατά η διαδρομή αποθήκευσης, κάτι που το άρθρο απόδοσης μεγάλων βιβλίων εργασίας αναλαμβάνει απευθείας. Και τα δύο APIs συγχώνευσης, οι δομικές λειτουργίες επεξεργασίας, και τα demo templates έρχονται με το HotXLS Delphi Component