Artykuł techniczny

Format TDateTimePicker w Delphi 5: DateTime_SetFormat

TDateTimePicker zyskał właściwość Format gdzieś między Delphi 5 a Delphi 6. Dokładna granica wersji jest rzadko udokumentowana w sposób, któremu można ufać, a kiedy na nią trafisz, kompilator po prostu poinformuje cię, że identyfikator nie istnieje. Linia, która kompiluje się i działa w każdej nowszej wersji Delphi, zawodzi po cichu lub całkowicie w starszej wersji, w zależności od struktury projektu

Podstawową kontrolką jest wspólna kontrolka Windows DATETIMEPICK_CLASS z pliku comctl32.dll, która od zawsze akceptowała ciąg formatu poprzez komunikat DTM_SETFORMAT. VCL w nowszych wersjach Delphi opakowuje ten komunikat we właściwość Format. W Delphi 5 musisz wysłać ten komunikat samodzielnie za pomocą makra DateTime_SetFormat zadeklarowanego w module CommCtrl

Wzorzec kompilacji warunkowej

Standardowym podejściem jest blok kompilacji warunkowej oparty na symbolu wersji. Delphi 5 definiuje VER130; możesz sprawdzić to bezpośrednio lub zdefiniować własny symbol D5 w opcjach projektu, aby uczynić etykietę gałęzi bardziej czytelną w całym kodzie. W obu przypadkach gałąź dla Delphi 5 wywołuje DateTime_SetFormat z uchwytem kontrolki; każda inna gałąź przypisuje właściwość:

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

Dodaj CommCtrl do sekcji uses w module, który zawiera gałąź dla Delphi 5. Nie ma potrzeby dodawania tego bezwarunkowo; jeśli wolisz ograniczyć zasięg zależności, opakuj wpis w uses w ten sam warunek:

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

Składnia ciągu formatu

Ciąg formatu przekazywany do DateTime_SetFormat jest zgodny z formatem kontrolki wyboru daty i godziny w systemie Windows, a nie z tokenami FormatDateTime w Delphi. Oba wyglądają podobnie, ale nie można ich stosować zamiennie. Windows używa d dla dnia miesiąca (bez zera wiodącego), dd dla dnia z zerem wiodącym, M dla numeru miesiąca, MM dla miesiąca z zerem wiodącym, yy dla dwucyfrowego roku i yyyy dla czterocyfrowego roku. Pola czasu używają h/hh dla godzin w formacie 12-godzinnym, H/HH dla 24-godzinnego, m/mm dla minut, s/ss dla sekund. Tekst dosłowny umieszcza się w pojedynczych cudzysłowach wewnątrz ciągu formatu, więc 'dd/MM/yyyy' jest poprawne, ale 'dd '<literal>' MM' wymagałoby wbudowanych par pojedynczych cudzysłowów

Dla porównania, FormatDateTime w Delphi używa d/dd/ddd/dddd dla formatów dni o różnej semantyce i m dla miesiąca (nie M). Jeśli skopiujesz ciąg formatu z wywołania FormatDateTime i przekażesz go bezpośrednio do DateTime_SetFormat, pola miesiąca i minuty prawdopodobnie zostaną zamienione na wyjściu. Napisz ciąg formatu kontrolki od podstaw zgodnie z dokumentacją systemu Windows, zamiast konwertować go z formatu Delphi

Ustawienia regionalne a globalna zmienna ShortDateFormat

Zmiana globalnej zmiennej ShortDateFormat w Delphi nie wpływa na utworzony już komponent TDateTimePicker. Kontrolka odczytuje ustawienia regionalne z systemu Windows w momencie tworzenia, a następnie renderuje zgodnie z otrzymywanymi komunikatami, a nie ze zmiennymi globalnymi RTL Delphi. Oznacza to, że ustawienie ShortDateFormat w FormCreate nie zmieni tego, co wyświetla kontrolka. Potrzebujesz funkcji DateTime_SetFormat (lub właściwości Format w nowszych wersjach Delphi), aby nadpisać własny ciąg formatu kontrolki po utworzeniu jej uchwytu

W przypadku aplikacji wdrażanych międzynarodowo przetestuj ciąg formatu na maszynie z ustawieniami regionalnymi innymi niż amerykańskie. Windows może nadpisać pusty (blank lub nil) ciąg formatu domyślnym dla danych ustawień regionalnych, ale jawny, niepusty ciąg całkowicie zastępuje ustawienia regionalne. Zazwyczaj jest to pożądane zachowanie, ale oznacza to, że amerykański wzorzec, taki jak 'MM/dd/yyyy', pojawi się w niezmienionej formie u użytkowników, którzy zwyczajowo oczekują dd/MM/yyyy. Jeśli kontrolka wyboru daty jest skierowana do użytkownika, a nie jest czystym polem wprowadzania danych, rozważ pozostawienie ustawienia domyślnego kontrolki (przekaż nil do DateTime_SetFormat), aby automatycznie dostosowywała się do ustawień regionalnych użytkownika

Ponowne tworzenie uchwytu

Ustawienie formatu należy do uchwytu okna, a nie do żadnego pola obiektu VCL. Jeśli uchwyt kontrolki zostanie zniszczony i utworzony ponownie, co VCL może zrobić podczas zmiany rodzica kontrolki lub zmiany niektórych właściwości stylu w czasie wykonywania, format powróci do domyślnego dla systemu Windows. Bezpiecznym miejscem na zastosowanie DateTime_SetFormat jest procedura obsługi uruchamiana po utworzeniu uchwytu: nadpisz CreateWnd w klasie pochodnej i wywołaj ją tam, lub zastosuj w zdarzeniu OnEnter lub OnShow dopiero po upewnieniu się, że uchwyt jest aktywny. Jednorazowe ustawienie tego w FormCreate często wystarcza dla prostych formularzy, ale nie jest niezawodne, jeśli formularz wykonuje dynamiczne przełączanie stylów lub zmianę rodzica kontrolki

To samo ograniczenie dotyczy właściwości Format w VCL w nowszych wersjach Delphi; wewnętrznie wywołuje ona ten sam komunikat systemu Windows i podlega temu samemu cyklowi życia uchwytu. Różnica polega na tym, że setter właściwości w VCL sprawdza, czy uchwyt jest przydzielony przed wykonaniem wywołania, i automatycznie ponownie stosuje format podczas CreateWnd. Surowe wywołanie DateTime_SetFormat nie ma takiego zabezpieczenia, więc gałąź dla Delphi 5 musi być bardziej przemyślana pod kątem momentu uruchomienia

Usunięcie obejścia

Gdy projekt rezygnuje ze wsparcia dla Delphi 5, blok warunkowy staje się martwym kodem. Usuń gałąź {$IFDEF D5} i powiązany import CommCtrl, pozostawiając tylko bezpośrednie przypisanie właściwości Format. Zachowaj usunięcie w jednym commicie, aby łatwo było zidentyfikować problem, jeśli coś nieoczekiwanie zepsuje się w kompilatorze, którego nie testowałeś. Sam ciąg formatu pozostaje ten sam; zmienia się tylko mechanizm jego dostarczania