Technical Article

Формат на TDateTimePicker в Delphi 5: DateTime_SetFormat

Компонентът TDateTimePicker получи свойството Format някъде между версии Delphi 5 и Delphi 6. Точната граница между версиите рядко е документирана по начин, на който да се доверите, и когато се сблъскате с това, компилаторът просто ви казва, че идентификаторът не съществува. Редът, който се компилира и работи на всяко по-ново Delphi, се проваля тихо или явно на старата версия, в зависимост от структурата на проекта.

Базовият компонент е стандартната системна контролка на Windows DATETIMEPICK_CLASS от comctl32.dll и тя винаги е приемала низ за форматиране чрез съобщението DTM_SETFORMAT. VCL библиотеката на Delphi обвива това съобщение в свойството Format в по-новите версии. При Delphi 5 изпращате съобщението сами чрез макроса обвивка DateTime_SetFormat, деклариран в модула CommCtrl.

Моделът с условна компилация

Стандартният подход е блок за условна компилация, свързан с дефиниран символ за версия. Delphi 5 дефинира VER130; можете да го тествате директно или да дефинирате собствен символ D5 в опциите на проекта, за да направите разклонението в кода по-четливо. В двата случая клонът за Delphi 5 извиква DateTime_SetFormat с манипулатора (handle) на контролката; всеки друг клон просто присвоява стойност на свойството:

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

Добавете CommCtrl към секцията uses на модула, който съдържа клона за Delphi 5. Няма нужда да го добавяте безусловно; ако предпочитате да запазите зависимостта изолирана, обвийте вноса в същата условна конструкция:

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

Синтаксис на низа за форматиране

Низът за форматиране, подаден на DateTime_SetFormat, следва формата на Windows за избор на дата и час, а не означенията на FormatDateTime в Delphi. Двата формата изглеждат сходно, но не са взаимозаменяеми. Windows използва d за ден от месеца (без водеща нула), dd за ден с водеща нула, M за номер на месеца, MM за месец с водеща нула, yy за двуцифрена година и yyyy за четирицифрена година. Времевите полета използват h/hh за 12-часов формат, H/HH за 24-часов формат, m/mm за минути, s/ss за секунди. Текстът в низа се поставя в единични кавички, така че 'dd/MM/yyyy' вече е коректно, но за вграждане на литерали в низа ще се изискват вложени двойки единични кавички.

За разлика от това, функцията FormatDateTime на Delphi използва d/dd/ddd/dddd за ден с различна семантика и m за месец (а не M). Ако копирате форматиращ низ от извикване на FormatDateTime и го подадете директно на DateTime_SetFormat, полетата за месец и минута вероятно ще бъдат разменени в изхода. Напишете форматиращия низ на контролката от нулата съгласно документацията на Windows, вместо да го преобразувате от формат на Delphi.

Регионални настройки и глобалната променлива ShortDateFormat

Промяната на глобалната променлива ShortDateFormat на Delphi не влияе на вече създаден компонент TDateTimePicker. Изборът на дата чете регионалните настройки от Windows при създаването си и след това се рендерира според съобщенията, които получава, а не според глобалните променливи на Delphi RTL. Това означава, че задаването на ShortDateFormat в FormCreate няма да промени изгледа на контролката. Трябва да използвате DateTime_SetFormat (или свойството Format в по-новите версии), за да презапишете форматиращия низ, след като нейният манипулатор (handle) вече съществува.

За приложения, които се разпространяват в различни страни, тествайте низа за форматиране на машина с регионални настройки, различни от тези за САЩ. Windows може да замени празен или nil формат с локалния по подразбиране, но изрично зададен не-nil низ заменя регионалната настройка изцяло. Това обикновено е търсеният ефект, но означава, че шаблон като 'MM/dd/yyyy' ще се показва в този си вид дори за потребители, които по конвенция очакват dd/MM/yyyy. Ако изборът на дата е предназначен за потребителя, а не е просто поле за въвеждане на данни, помислете дали да не оставите настройките по подразбиране (като подадете nil на DateTime_SetFormat), за да следва той автоматично локала на потребителя.

Повторно създаване на манипулатора (Handle recreation)

Настройката на формата принадлежи на системния манипулатор (handle) на прозореца, а не на поле в обекта на VCL. Ако манипулаторът на контролката бъде унищожен и създаден наново (което VCL може да направи при смяна на родителския контейнер или промяна на някои свойства на стила по време на работа), форматът се връща към този на Windows по подразбиране. Безопасното място за прилагане на DateTime_SetFormat е в процедура, която се изпълнява след създаването на манипулатора: препокрийте CreateWnd в наследник на класа и го извикайте там, или го приложете в събитието OnEnter или OnShow, след като се уверите, че манипулаторът е активен. Задаването му веднъж в FormCreate често е достатъчно за прости форми, но не е надеждно при динамична смяна на стилове или родителски контейнери.

Същото ограничение важи и за свойството Format на VCL в по-новите версии на Delphi; вътрешно то извиква същото съобщение на Windows и зависи от същия жизнен цикъл на манипулатора. Разликата е, че методът за запис на свойството във VCL проверява дали манипулаторът е заделен преди извикването и автоматично прилага отново формата при CreateWnd. Директното извикване на DateTime_SetFormat няма такава защита, затова разклонението за Delphi 5 трябва да бъде изпълнявано по-внимателно.

Премахване на заобиколното решение

Когато проектът преустанови поддръжката за Delphi 5, условният блок става излишен. Премахнете разклонението {$IFDEF D5} и локалния внос на CommCtrl, оставяйки само директното присвояване на свойството Format. Направете това премахване в рамките на един commit, за да бъде лесно да се идентифицира евентуален неочакван богатствен проблем при компилатор, който не сте тествали. Самият низ за форматиране остава същият; променя се само начинът на неговото подаване.