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

Μορφή TDateTimePicker στο Delphi 5: DateTime_SetFormat

Το TDateTimePicker απέκτησε μια ιδιότητα Format κάπου μεταξύ του Delphi 5 και του Delphi 6. Το ακριβές όριο έκδοσης σπάνια τεκμηριώνεται με τρόπο που μπορείτε να εμπιστευτείτε, και όταν το πετύχετε, ο μεταγλωττιστής απλώς σας λέει ότι το αναγνωριστικό δεν υπάρχει. Η γραμμή που μεταγλωττίζεται και λειτουργεί σε κάθε νεότερο Delphi που έχετε, αποτυγχάνει αθόρυβα ή εντελώς στο παλιό, ανάλογα με τη δομή του έργου

Το υποκείμενο στοιχείο ελέγχου είναι το κοινό στοιχείο ελέγχου DATETIMEPICK_CLASS των Windows από το comctl32.dll, και πάντα δεχόταν μια συμβολοσειρά μορφοποίησης (format string) μέσω του μηνύματος DTM_SETFORMAT. Το VCL του Delphi τυλίγει (wraps) αυτό το μήνυμα στην ιδιότητα Format σε νεότερες εκδόσεις. Στο Delphi 5, στέλνετε οι ίδιοι το μήνυμα μέσω του μακρο-περιτυλίγματος DateTime_SetFormat που δηλώνεται στο CommCtrl

Το μοτίβο υπό όρους μεταγλώττισης (conditional compilation)

Η τυπική προσέγγιση είναι ένα μπλοκ υπό όρους του μεταγλωττιστή που βασίζεται σε ένα σύμβολο έκδοσης. Το Delphi 5 ορίζει το VER130. μπορείτε να δοκιμάσετε εναντίον του απευθείας ή να ορίσετε το δικό σας σύμβολο D5 στις επιλογές έργου (project options) για να κάνετε την ετικέτα διακλάδωσης πιο ευανάγνωστη σε ολόκληρη τη βάση κώδικα (codebase). Είτε έτσι είτε αλλιώς, ο κλάδος του Delphi 5 καλεί το DateTime_SetFormat με τη λαβή του στοιχείου ελέγχου (control handle). Κάθε άλλος κλάδος εκχωρεί την ιδιότητα:

{$IFDEF D5}
  DateTime_SetFormat(DateTimePicker1.Handle, PChar('MM/dd/yyyy'));
{$ELSE}
  DateTimePicker1.Format := 'MM/dd/yyyy';
{$ENDIF}

Προσθέστε το CommCtrl στη ρήτρα (clause) uses για τη μονάδα (unit) που περιέχει τον κλάδο του Delphi 5. Δεν υπάρχει λόγος να το προσθέσετε άνευ όρων. εάν προτιμάτε να διατηρήσετε την εξάρτηση περιορισμένη, τυλίξτε την καταχώρηση uses στην ίδια συνθήκη:

uses
  ...,
{$IFDEF D5}
  CommCtrl,
{$ENDIF}
  ...;

Σύνταξη συμβολοσειράς μορφοποίησης

Η συμβολοσειρά μορφοποίησης που διαβιβάζεται στο DateTime_SetFormat ακολουθεί τη μορφή επιλογής ημερομηνίας-ώρας των Windows (Windows date-time picker format), όχι τα διακριτικά FormatDateTime του Delphi. Τα δύο μοιάζουν αλλά δεν είναι εναλλάξιμα. Τα Windows χρησιμοποιούν d για την ημέρα του μήνα (χωρίς αρχικό μηδενικό), dd για την ημέρα με συμπλήρωση μηδενικών, M για τον αριθμό του μήνα, MM για τον μήνα με συμπλήρωση μηδενικών, yy για διψήφιο έτος και yyyy για τετραψήφιο έτος. Τα πεδία ώρας χρησιμοποιούν h/hh για ώρες 12ώρου, H/HH για 24ώρου, m/mm για λεπτά, s/ss για δευτερόλεπτα. Το κυριολεκτικό κείμενο (literal text) μπαίνει μέσα σε μονά εισαγωγικά μέσα στη συμβολοσειρά μορφοποίησης, οπότε το 'dd/MM/yyyy' είναι ήδη σωστό, αλλά το 'dd '<literal>' MM' θα απαιτούσε ενσωματωμένα ζεύγη μονών εισαγωγικών

Η FormatDateTime του Delphi, αντίθετα, χρησιμοποιεί d/dd/ddd/dddd για μορφές ημέρας με διαφορετική σημασιολογία, και m για το μήνα (όχι M). Αν αντιγράψετε μια συμβολοσειρά μορφοποίησης από μια κλήση FormatDateTime και την παραδώσετε απευθείας στο DateTime_SetFormat, τα πεδία του μήνα και των λεπτών πιθανότατα θα ανταλλαχθούν στην έξοδο. Γράψτε τη συμβολοσειρά μορφοποίησης του στοιχείου ελέγχου από την αρχή, σύμφωνα με την τεκμηρίωση των Windows, αντί να μετατρέψετε από μια μορφή Delphi

Τοπικές ρυθμίσεις (Regional settings) και το καθολικό ShortDateFormat

Η αλλαγή της καθολικής μεταβλητής (global variable) ShortDateFormat του Delphi δεν επηρεάζει ένα TDateTimePicker που έχει ήδη δημιουργηθεί. Ο επιλογέας διαβάζει τις τοπικές ρυθμίσεις από τα Windows κατά τον χρόνο δημιουργίας και έκτοτε αποδίδει σύμφωνα με τα μηνύματα που λαμβάνει, όχι με βάση τα καθολικά (globals) του Delphi RTL. Αυτό σημαίνει ότι η ρύθμιση του ShortDateFormat στο FormCreate δεν θα αλλάξει αυτό που εμφανίζει το στοιχείο ελέγχου. Χρειάζεστε το DateTime_SetFormat (ή την ιδιότητα Format στα νεότερα Delphi) για να αντικαταστήσετε τη δική του συμβολοσειρά μορφοποίησης του στοιχείου ελέγχου, αφού υπάρχει η λαβή

Για εφαρμογές που αναπτύσσονται διεθνώς, δοκιμάστε τη συμβολοσειρά μορφοποίησης σε ένα μηχάνημα με τοπική ρύθμιση εκτός ΗΠΑ. Τα Windows μπορούν να παρακάμψουν (override) μια κενή ή μηδενική (nil) συμβολοσειρά μορφοποίησης με την προεπιλογή των τοπικών ρυθμίσεων περιοχής (locale default), αλλά μια ρητή μη-nil συμβολοσειρά αντικαθιστά εξ ολοκλήρου την τοπική ρύθμιση. Αυτό είναι συνήθως αυτό που θέλετε, αλλά σημαίνει ότι ένα αμερικανοκεντρικό μοτίβο όπως το 'MM/dd/yyyy' θα εμφανιστεί ως έχει για χρήστες που αναμένουν dd/MM/yyyy κατά σύμβαση. Εάν ο επιλογέας ημερομηνίας απευθύνεται στον χρήστη (user-facing) αντί για ένα καθαρό πεδίο εισαγωγής δεδομένων, σκεφτείτε να αφήσετε το στοιχείο ελέγχου να λάβει την προεπιλογή (μεταβιβάστε nil στο DateTime_SetFormat) έτσι ώστε να ακολουθεί αυτόματα τις τοπικές ρυθμίσεις του χρήστη

Αναδημιουργία λαβής (Handle recreation)

Η ρύθμιση μορφοποίησης ανήκει στη λαβή του παραθύρου (window handle), όχι σε οποιοδήποτε πεδίο αντικειμένου VCL. Εάν η λαβή του στοιχείου ελέγχου καταστραφεί και δημιουργηθεί ξανά, κάτι που το VCL μπορεί να κάνει όταν αλλάζετε το γονικό στοιχείο (reparent) ή αλλάζετε ορισμένες ιδιότητες στυλ κατά τον χρόνο εκτέλεσης (runtime), η μορφοποίηση επανέρχεται στην προεπιλογή των Windows. Το ασφαλές μέρος για να εφαρμόσετε το DateTime_SetFormat είναι σε έναν χειριστή (handler) που εκτελείται μετά τη δημιουργία λαβής: παρακάμψτε το CreateWnd σε μια κλάση απογόνου (descendant class) και καλέστε το εκεί, ή εφαρμόστε το στο συμβάν OnEnter ή OnShow μόνο αφού επιβεβαιώσετε ότι η λαβή είναι ενεργή. Η ρύθμισή του μία φορά στο FormCreate είναι συχνά επαρκής για απλές φόρμες, αλλά δεν είναι αξιόπιστη εάν η φόρμα κάνει δυναμική εναλλαγή στυλ ή αλλαγή γονέα στοιχείου ελέγχου

Ο ίδιος περιορισμός ισχύει και για την ιδιότητα Format του VCL σε νεότερες εκδόσεις του Delphi. εσωτερικά καλεί το ίδιο μήνυμα των Windows και αντιμετωπίζει τον ίδιο κύκλο ζωής λαβής. Η διαφορά είναι ότι ο setter της ιδιότητας VCL ελέγχει εάν έχει εκχωρηθεί (allocated) η λαβή πριν καλέσει μέσω αυτού, και εφαρμόζει ξανά τη μορφή αυτόματα στο CreateWnd. Η ακατέργαστη κλήση DateTime_SetFormat δεν έχει τέτοια προστασία, οπότε ο κλάδος του Delphi 5 πρέπει να είναι πιο σκόπιμος (deliberate) σχετικά με το πότε τρέχει

Κατάργηση της λύσης (workaround)

Όταν ένα έργο εγκαταλείπει την υποστήριξη του Delphi 5, το μπλοκ υπό όρους γίνεται νεκρό βάρος. Αφαιρέστε τον κλάδο {$IFDEF D5} και την εισαγωγή (import) του περιορισμένου (scoped) CommCtrl, αφήνοντας μόνο την άμεση εκχώρηση της ιδιότητας Format. Κρατήστε την αφαίρεση σε ένα μόνο commit, ώστε να είναι εύκολο να εντοπιστεί εάν κάτι σπάσει απροσδόκητα σε έναν μεταγλωττιστή που δεν δοκιμάσατε. Η ίδια η συμβολοσειρά μορφοποίησης παραμένει ίδια. μόνο ο μηχανισμός παράδοσης αλλάζει