Komponenta TDateTimePicker získala vlastnost (property) Format někdy mezi verzí Delphi 5 a Delphi 6. Přesná hraniční verze bývá zřídkakdy zdokumentovaná tak, abyste jí mohli plně důvěřovat, a ve chvíli, kdy na ni narazíte, kompilátor pouze oznámí, že daný identifikátor (identifier) neexistuje. Stejný řádek kódu, který se bez problémů zkompiluje a funguje na každém novějším Delphi, může na té starší verzi buď tiše selhat, nebo přímo způsobit chybu při kompilaci – to se odvíjí od toho, jak je projekt strukturován
Komponenta samotná staví na prvku Windows s názvem DATETIMEPICK_CLASS (common control z balíčku comctl32.dll), přičemž ten formátovací řetězec přijímá přes datovou zprávu DTM_SETFORMAT celou dobu. Knihovna Delphi (VCL) u novějších verzí tuto zprávu šikovně obaluje právě zmíněnou vlastností (property) s názvem Format. Co se týče verze pro Delphi 5, u ní je nezbytné zprávu k odeslání vybavit úplně svépomocí za použití makra DateTime_SetFormat zapsaného u knihovny CommCtrl
Princip používání podmínek při kompilaci
Doporučovaný přístup spoléhá na definování specifického bloku prostřednictvím kompilátorové podmínky závislé na použité verzi (verze se vyhodnocuje díky příslušnému symbolu). Delphi 5 standardně definuje VER130; tuto hodnotu je možné testovat přímo ve vašem kódu. Alternativně lze ale vytvořit váš vlastní a snáze čitelný štítek pro podmíněný kód pro dané větvení úpravou a přidáním parametru například ve tvaru D5 přímo do nastavení v projektových volbách, díky čemuž je potom příslušná větev čitelnější napříč celou kódovou základnou. Obojí ale řeší přesně to stejné: větev určená pro Delphi 5 jednoduše spustí makro DateTime_SetFormat pro odpovídající parametr handle ovládacího prvku, přičemž pro veškeré ostatní větve už zůstává přidělena odpovídající vlastnost (property):
{$IFDEF D5}
DateTime_SetFormat(DateTimePicker1.Handle, PChar('MM/dd/yyyy'));
{$ELSE}
DateTimePicker1.Format := 'MM/dd/yyyy';
{$ENDIF}
Připojte zápis prvku CommCtrl do seznamu uses v konkrétní jednotce, ze které vaše navrhovaná větev Delphi 5 přímo vychází. Není vůbec žádoucí k této úpravě připojovat uvedený prvek nějak paušálně do každé další verze nezávisle a nevybíravě; pokud proto navíc chcete závislost (dependency) ještě více a bezpečně omezit jen v rámci rozsahu požadovaného oboru (scope), jednoduše doplňte samotný záznam uses totožnou kompilátorovou omezovací podmínkou k oddělení zápisu k připojení k modulu pro Delphi 5 do vyhrazeného celku:
uses
...,
{$IFDEF D5}
CommCtrl,
{$ENDIF}
...;
Skladba formátovacího řetězce
Formátovací řetězec předávaný funkci DateTime_SetFormat se řídí formátem Windows date-time pickeru, nikoli tokeny Delphi funkce FormatDateTime. Ty dva vypadají podobně, ale nejsou zaměnitelné. Windows používá d pro den v měsíci (bez nuly na začátku), dd pro den doplněný nulou, M pro číslo měsíce, MM pro měsíc doplněný nulou, yy pro dvojciferný rok a yyyy pro čtyřciferný rok. Časová pole používají h/hh pro 12hodinové hodiny, H/HH pro 24hodinové, m/mm pro minuty, s/ss pro sekundy. Doslovný text patří uvnitř jednoduchých uvozovek uvnitř formátovacího řetězce, takže 'dd/MM/yyyy' je již správně, ale 'dd '<literal>' MM' by vyžadovalo vložené páry jednoduchých uvozovek
Delphi funkce FormatDateTime naproti tomu používá d/dd/ddd/dddd pro tvary dne s odlišnou sémantikou a m pro měsíc (nikoli M). Pokud zkopírujete formátovací řetězec z volání FormatDateTime a předáte jej přímo funkci DateTime_SetFormat, pole měsíce a minuty se ve výstupu pravděpodobně prohodí. Napište formátovací řetězec ovládacího prvku od základu podle dokumentace Windows, místo abyste jej převáděli z formátu Delphi
Regionální nastavení a globální proměnná ShortDateFormat
Změna globální proměnné Delphi ShortDateFormat neovlivní TDateTimePicker, který už byl vytvořen. Picker načte regionální nastavení z Windows v okamžiku vytvoření a poté se řídí zprávami, které přijímá, nikoli globálními proměnnými RTL Delphi. To znamená, že nastavení ShortDateFormat v FormCreate nezmění, co ovládací prvek zobrazuje. Potřebujete DateTime_SetFormat (nebo vlastnost Format u novějšího Delphi), abyste přepsali vlastní formátovací řetězec ovládacího prvku poté, co handle existuje
U aplikací nasazovaných mezinárodně otestujte formátovací řetězec na počítači s jiným než americkým regionálním nastavením. Windows může prázdný nebo nil formátovací řetězec přepsat výchozím nastavením národního prostředí, ale explicitní ne-nil řetězec zcela nahradí regionální nastavení. To je obvykle to, co chcete, ale znamená to, že vzor orientovaný na USA jako 'MM/dd/yyyy' se zobrazí beze změny i uživatelům, kteří podle konvence očekávají dd/MM/yyyy. Pokud je datový picker určen uživateli, a ne čistě pro zadávání dat, zvažte ponechání výchozí hodnoty ovládacího prvku (předání nil funkci DateTime_SetFormat), aby se automaticky řídil národním prostředím uživatele
Znovuvytvoření handle
Nastavení formátu patří k handle okna, nikoli k žádnému poli objektu VCL. Pokud je handle ovládacího prvku zničen a znovu vytvořen, což VCL může udělat při přeřazení ovládacího prvku k jinému rodiči nebo při změně určitých vlastností stylu za běhu, formát se vrátí na výchozí hodnotu Windows. Bezpečné místo pro použití DateTime_SetFormat je v obslužné rutině, která běží po vytvoření handle: přepište CreateWnd v potomkovské třídě a zavolejte jej tam, nebo jej použijte v události OnEnter nebo OnShow, ale až po ověření, že handle existuje. Nastavení jednou ve FormCreate často stačí pro jednoduché formuláře, ale není spolehlivé, pokud formulář provádí dynamické přepínání stylů nebo přeřazování ovládacích prvků
Zcela stejné omezení platí i pro vlastnost (property) Format u knihovny VCL na novějších verzích Delphi; interně totiž volá stejnou zprávu (message) systému Windows a čelí naprosto stejnému životnímu cyklu handle (identifikátoru). Rozdíl ale spočívá v tom, že setter (nastavovací metoda) vlastnosti VCL ověří, zda byl handle alokován, ještě před tím, než volání propustí dále, a zároveň formát automaticky znovu aplikuje v rámci CreateWnd. Syrové volání makra DateTime_SetFormat žádnou takovou ochranu nemá, takže větev kódu pro Delphi 5 musí být o poznání obezřetnější v tom, kdy se vlastně spustí
Odstranění tohoto obchozího řešení (workaround)
Ve chvíli, kdy projekt ukončí podporu verze Delphi 5, se podmíněný blok stane jen mrtvou zátěží. Odstraňte větev se značením {$IFDEF D5} a také omezený import knihovny CommCtrl tak, aby v kódu zůstalo pouze přímé přiřazení k vlastnosti Format. Toto odstranění ideálně ponechte ve formě jednoho samostatného commitu, aby se případně dalo snadno identifikovat, pokud se u překladače, který jste předtím netestovali, nečekaně něco porouchá. Samotný formátovací řetězec pochopitelně zůstává úplně stejný; mění se pouze mechanismus jeho doručení