Teknisk artikel

TDateTimePicker-format i Delphi 5: DateTime_SetFormat

TDateTimePicker fick en Format-egenskap någonstans mellan Delphi 5 och Delphi 6. Den exakta versionsgränsen är sällan dokumenterad på ett sätt som du kan lita på, och när du stöter på den, berättar kompilatorn helt enkelt att identifieraren inte existerar. Den rad som kompileras och fungerar på alla nyare Delphi-versioner du har misslyckas tyst eller helt och hållet på den gamla, beroende på hur projektet är strukturerat

Den underliggande kontrollen är DATETIMEPICK_CLASS, en standardkontroll (common control) för Windows från comctl32.dll, och den har alltid accepterat en formatsträng genom DTM_SETFORMAT-meddelandet. Delphis VCL omsluter det meddelandet i Format-egenskapen i nyare versioner. I Delphi 5 skickar du meddelandet själv via makro-omslaget DateTime_SetFormat som deklareras i CommCtrl

Mönstret för villkorlig kompilering

Standardtillvägagångssättet är ett kompilatorvillkorligt block (compiler-conditional block) nycklat på en versionssymbol. Delphi 5 definierar VER130; du kan testa mot den direkt eller definiera din egen D5-symbol i projektalternativen för att göra förgreningsetiketten mer läsbar i hela kodbasen. Oavsett vilket, anropar Delphi 5-grenen DateTime_SetFormat med kontrollhandtaget; alla andra grenar tilldelar egenskapen:

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

Lägg till CommCtrl i uses-satsen för den enhet som innehåller Delphi 5-grenen. Det finns ingen anledning att lägga till det ovillkorligt; om du föredrar att hålla beroendet avgränsat, omslut uses-posten i samma villkor:

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

Syntax för formatsträng

Formatsträngen som skickas till DateTime_SetFormat följer formatet för datum- och tidsväljaren i Windows, inte Delphis FormatDateTime-tokens. De två ser liknande ut men är inte utbytbara. Windows använder d för dag-i-månaden (utan inledande nolla), dd för nollutfylld dag, M för månadsnummer, MM för nollutfylld månad, yy för tvåsiffrigt år och yyyy för fyrsiffrigt år. Tidsfält använder h/hh för 12-timmars format, H/HH för 24-timmars, m/mm för minuter, s/ss för sekunder. Bokstavlig text hamnar inuti enkla citattecken inuti formatsträngen, så 'dd/MM/yyyy' är redan korrekt, men 'dd '<literal>' MM' skulle kräva inbäddade enkla citattecken-par

Delphis FormatDateTime använder däremot d/dd/ddd/dddd för dagformer med annorlunda semantik, och m för månad (inte M). Om du kopierar en formatsträng från ett FormatDateTime-anrop och skickar den direkt till DateTime_SetFormat kommer månads- och minutfälten förmodligen att förväxlas i utdata. Skriv kontrollens formatsträng från grunden utifrån Windows-dokumentationen istället för att konvertera från ett Delphi-format

Regionala inställningar och den globala ShortDateFormat

Att ändra Delphis globala ShortDateFormat-variabel påverkar inte en TDateTimePicker som redan har skapats. Väljaren läser de regionala inställningarna från Windows vid skapandetillfället och renderar därefter utifrån de meddelanden den tar emot, inte från Delphis RTL-globaler. Det innebär att en inställning av ShortDateFormat i FormCreate inte kommer att förändra vad kontrollen visar. Du behöver DateTime_SetFormat (eller Format-egenskapen i nyare Delphi) för att åsidosätta kontrollens egen formatsträng efter att handtaget existerar

För applikationer som distribueras internationellt, testa formatsträngen på en dator med en icke-amerikansk regional inställning. Windows kan åsidosätta en tom eller nollställd formatsträng med det lokala standardvärdet, men en explicit icke-noll-sträng ersätter den regionala inställningen helt. Det är oftast det du vill ha, men det betyder att ett USA-centrerat mönster som 'MM/dd/yyyy' kommer att visas oförändrat för användare som förväntar sig konventionen dd/MM/yyyy. Om datumväljaren är synlig för användaren snarare än ett rent datainmatningsfält, överväg att låta kontrollen ha sitt standardvärde (skicka nil till DateTime_SetFormat) så att den följer användarens lokala inställningar automatiskt

Återskapande av handtag

Formatinställningen hör till fönsterhandtaget (window handle), inte till något fält i VCL-objektet. Om kontrollens handtag förstörs och återskapas, vilket VCL kan göra när du byter förälder på en kontroll (reparent) eller ändrar vissa stilegenskaper vid körning, återgår formatet till standardvärdet för Windows. Det säkra stället att tillämpa DateTime_SetFormat är i en hanterare som körs efter att handtaget skapats: åsidosätt CreateWnd i en härledd klass och anropa det där, eller tillämpa det i OnEnter- eller OnShow-händelsen först efter att du har bekräftat att handtaget är aktivt. Att ställa in det en gång i FormCreate är ofta tillräckligt för enkla formulär, men det är inte tillförlitligt om formuläret gör dynamiska stilbyten eller byter förälder på kontroller

Samma begränsning gäller för VCL:s Format-egenskap i nyare Delphi-versioner; internt anropar den samma Windows-meddelande och möter samma handtagscykel (handle lifecycle). Skillnaden är att VCL-egenskapens setter kontrollerar om handtaget är allokerat innan anropet skickas vidare, och tillämpa formatet automatiskt igen vid CreateWnd. Det råa DateTime_SetFormat-anropet saknar sådant skydd, så Delphi 5-grenen måste vara mer överlagd med när den körs

Att ta bort omvägen

När ett projekt slutar stödja Delphi 5, blir det villkorliga blocket döttvikt. Ta bort {$IFDEF D5}-grenen och den avgränsade CommCtrl-importen och behåll endast den direkta Format-egenskapstilldelningen. Behåll borttagningen i en enda commit så att det är lätt att identifiera om något oväntat går sönder på en kompilator du inte testat. Själva formatsträngen förblir densamma; endast leveransmekanismen förändras