Technischer Artikel

TDateTimePicker-Format in Delphi 5: DateTime_SetFormat

TDateTimePicker erhielt irgendwann zwischen Delphi 5 und Delphi 6 eine Format-Eigenschaft. Die genaue Versionsgrenze ist selten verlässlich dokumentiert, und wenn Sie darauf stoßen, teilt Ihnen der Compiler einfach mit, dass der Bezeichner nicht existiert. Die Zeile, die in jeder neueren Delphi-Version kompiliert wird und funktioniert, schlägt in der alten Version je nach Projektstruktur stillschweigend oder vollständig fehl

Das zugrunde liegende Steuerelement ist das Windows-Standardsteuerelement DATETIMEPICK_CLASS aus der comctl32.dll, das schon immer eine Formatzeichenfolge über die Nachricht DTM_SETFORMAT akzeptiert hat. Die VCL von Delphi verpackt diese Nachricht in neueren Versionen in die Eigenschaft Format. Unter Delphi 5 senden Sie die Nachricht selbst über den in CommCtrl deklarierten Makro-Wrapper DateTime_SetFormat

Das Muster der bedingten Kompilierung

Der Standardansatz ist ein bedingter Compiler-Block, der auf einem Versionssymbol basiert. Delphi 5 definiert VER130; Sie können direkt darauf prüfen oder Ihr eigenes D5-Symbol in den Projektoptionen definieren, um die Verzweigungsbezeichnung in der gesamten Codebasis besser lesbar zu machen. In jedem Fall ruft der Delphi-5-Zweig DateTime_SetFormat mit dem Steuerelement-Handle auf; jeder andere Zweig weist die Eigenschaft zu:

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

Fügen Sie CommCtrl zur uses-Klausel für die Unit hinzu, die den Delphi-5-Zweig enthält. Es ist nicht nötig, dies bedingungslos hinzuzufügen; wenn Sie die Abhängigkeit lieber eingegrenzt halten möchten, hüllen Sie den uses-Eintrag in dieselbe Bedingung ein:

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

Syntax der Formatzeichenfolge

Die an DateTime_SetFormat übergebene Formatzeichenfolge folgt dem Format des Windows-Datum-Uhrzeit-Auswahlfelds, nicht den FormatDateTime-Tokens von Delphi. Die beiden sehen ähnlich aus, sind aber nicht austauschbar. Windows verwendet d für den Tag des Monats (ohne führende Null), dd für den Tag mit führender Null, M für die Monatsnummer, MM für den Monat mit führender Null, yy für das zweistellige Jahr und yyyy für das vierstellige Jahr. Zeitfelder verwenden h/hh für 12-Stunden-Stunden, H/HH für 24-Stunden-Stunden, m/mm für Minuten, s/ss für Sekunden. Wörtlicher Text wird in einfache Anführungszeichen innerhalb der Formatzeichenfolge gesetzt, sodass 'dd/MM/yyyy' bereits korrekt ist, aber 'dd '<literal>' MM' eingebettete Paare von einfachen Anführungszeichen erfordern würde

Delphis FormatDateTime verwendet im Gegensatz dazu d/dd/ddd/dddd für Tagesformen mit unterschiedlicher Semantik und m für den Monat (nicht M). Wenn Sie eine Formatzeichenfolge aus einem FormatDateTime-Aufruf kopieren und direkt an DateTime_SetFormat übergeben, werden Monats- und Minutenfelder in der Ausgabe wahrscheinlich vertauscht. Schreiben Sie die Formatzeichenfolge des Steuerelements basierend auf der Windows-Dokumentation von Grund auf neu, anstatt sie aus einem Delphi-Format zu konvertieren

Regionale Einstellungen und das globale ShortDateFormat

Das Ändern der globalen Delphi-Variablen ShortDateFormat wirkt sich nicht auf einen bereits erstellten TDateTimePicker aus. Das Auswahlfeld liest die regionalen Einstellungen von Windows zum Zeitpunkt der Erstellung aus und wird danach entsprechend den empfangenen Nachrichten gerendert, nicht nach den globalen VCL-Variablen. Das bedeutet, dass das Setzen von ShortDateFormat in FormCreate das angezeigte Steuerelement nicht ändert. Sie benötigen DateTime_SetFormat (oder die Format-Eigenschaft bei neueren Delphi-Versionen), um die eigene Formatzeichenfolge des Steuerelements zu überschreiben, nachdem das Handle existiert

Bei international eingesetzten Anwendungen sollten Sie die Formatzeichenfolge auf einem Rechner mit einer nicht-US-amerikanischen regionalen Einstellung testen. Windows kann eine leere oder nil-Formatzeichenfolge mit der Standardeinstellung für das Gebietsschema überschreiben, aber eine explizite Nicht-nil-Zeichenfolge ersetzt die regionale Einstellung vollständig. Das ist normalerweise das, was Sie wollen, bedeutet aber, dass ein US-zentriertes Muster wie 'MM/dd/yyyy' unverändert für Benutzer angezeigt wird, die konventionsgemäß dd/MM/yyyy erwarten. Wenn die Datumsauswahl eher dem Benutzer zugewandt ist als ein reines Dateneingabefeld, sollten Sie in Betracht ziehen, den Standard des Steuerelements zu belassen (indem Sie nil an DateTime_SetFormat übergeben), damit es automatisch dem Gebietsschema des Benutzers folgt

Neuerstellung von Handles

Die Formateinstellung gehört zum Fenster-Handle, nicht zu einem VCL-Objektfeld. Wenn das Handle des Steuerelements zerstört und neu erstellt wird, was die VCL tun kann, wenn Sie einem Steuerelement zur Laufzeit ein neues übergeordnetes Element zuweisen oder bestimmte Stileigenschaften ändern, wird das Format auf den Windows-Standard zurückgesetzt. Der sichere Ort für die Anwendung von DateTime_SetFormat ist in einem Handler, der nach der Erstellung des Handles ausgeführt wird: Überschreiben Sie CreateWnd in einer abgeleiteten Klasse und rufen Sie es dort auf, oder wenden Sie es im Ereignis OnEnter oder OnShow erst an, nachdem Sie bestätigt haben, dass das Handle aktiv ist. Es einmal in FormCreate zu setzen, reicht für einfache Formulare oft aus, ist aber nicht zuverlässig, wenn das Formular dynamische Stilumschaltungen oder Änderungen der übergeordneten Steuerelemente durchführt

Die gleiche Einschränkung gilt für die VCL-Eigenschaft Format in neueren Delphi-Versionen; intern ruft sie dieselbe Windows-Nachricht auf und hat denselben Handle-Lebenszyklus. Der Unterschied besteht darin, dass der Setter für die VCL-Eigenschaft überprüft, ob das Handle zugewiesen ist, bevor er aufruft, und das Format automatisch bei CreateWnd neu anwendet. Der reine Aufruf von DateTime_SetFormat hat keinen solchen Schutz, daher muss der Delphi-5-Zweig bedachter festlegen, wann er ausgeführt wird

Entfernen des Workarounds

Wenn ein Projekt die Unterstützung für Delphi 5 aufgibt, wird der bedingte Block zu totem Gewicht. Entfernen Sie den Zweig {$IFDEF D5} und den bereichsbezogenen CommCtrl-Import und lassen Sie nur die direkte Zuweisung der Format-Eigenschaft stehen. Nehmen Sie die Entfernung in einem einzigen Commit vor, damit leicht erkannt werden kann, ob bei einem Compiler, den Sie nicht getestet haben, unerwartet etwas kaputt geht. Die Formatzeichenfolge selbst bleibt gleich; nur der Übermittlungsmechanismus ändert sich