Техническая статья

Формат 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 с дескриптором элемента управления; любая другая ветка назначает свойство:

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

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

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' уже правильный, но 'dd '<literal>' MM' потребует встроенных пар одинарных кавычек

Напротив, FormatDateTime в Delphi использует d/dd/ddd/dddd для форматов дней с другой семантикой, и m для месяца (не M). Если вы скопируете строку формата из вызова FormatDateTime и передадите ее напрямую в DateTime_SetFormat, поля месяца и минут, вероятно, поменяются местами в выводе. Пишите строку формата элемента управления с нуля по документации Windows, а не конвертируйте из формата Delphi

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

Изменение глобальной переменной ShortDateFormat в Delphi не влияет на TDateTimePicker, который уже создан. Элемент выбора считывает региональные настройки из Windows во время создания и в дальнейшем отображается в соответствии с получаемыми сообщениями, а не глобальными переменными RTL Delphi. Это означает, что установка ShortDateFormat в FormCreate не изменит то, что отображает элемент управления. Вам нужен DateTime_SetFormat (или свойство Format в новых версиях Delphi), чтобы переопределить собственную строку формата элемента управления после того, как дескриптор будет существовать

Для приложений, развертываемых на международном уровне, протестируйте строку формата на машине с неамериканскими региональными настройками. Windows может переопределить пустую строку формата значением по умолчанию для локали, но явная непустая строка полностью заменяет региональные настройки. Обычно это то, что нужно, но это означает, что американоцентричный шаблон вроде 'MM/dd/yyyy' будет отображаться как есть для пользователей, которые ожидают dd/MM/yyyy по соглашению. Если элемент выбора даты ориентирован на пользователя, а не является чистым полем ввода данных, рассмотрите возможность использования настроек по умолчанию элемента управления (передайте nil в DateTime_SetFormat), чтобы он автоматически следовал локали пользователя

Пересоздание дескриптора

Настройка формата принадлежит дескриптору окна, а не какому-либо полю объекта 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. Оставьте удаление в одном коммите, чтобы было легко определить, если что-то неожиданно сломается на компиляторе, который вы не тестировали. Сама строка формата остается прежней; меняется только механизм доставки