技術記事

Delphi 5でのTDateTimePickerのフォーマット:DateTime_SetFormat

TDateTimePicker は、Delphi 5からDelphi 6の間のどこかで Format プロパティを獲得しました。正確なバージョンの境界が信頼できる形で文書化されていることは稀であり、それに直面したとき、コンパイラは単に識別子が存在しないと伝えてきます。お持ちのすべての新しいバージョンのDelphiでコンパイルされ機能する行が、プロジェクトの構造によっては、古いバージョンでは静かに失敗するか、完全に失敗します

基となるコントロールは comctl32.dll のWindows DATETIMEPICK_CLASS 共通コントロールであり、DTM_SETFORMAT メッセージを介してフォーマット文字列を常に受け入れてきました。DelphiのVCLは、新しいバージョンではそのメッセージを Format プロパティにラップしています。Delphi 5では、CommCtrl で宣言されている DateTime_SetFormat マクロラッパーを介して自分でメッセージを送信します

条件付きコンパイルのパターン

標準的なアプローチは、バージョンシンボルをキーとするコンパイラの条件ブロックです。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}

Delphi 5の分岐を含むユニットの uses 句に CommCtrl を追加します。無条件に追加する必要はありません。依存関係をスコープ内に収めたい場合は、uses エントリを同じ条件文でラップします:

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

フォーマット文字列の構文

DateTime_SetFormat に渡されるフォーマット文字列は、Delphiの FormatDateTime トークンではなく、Windowsの日付・時刻ピッカーのフォーマットに従います。この2つは似ていますが、互換性はありません。Windowsでは、月の日(先頭のゼロなし)に d、ゼロ埋めされた日に dd、月番号に M、ゼロ埋めされた月に MM、2桁の年に yy、4桁の年に yyyy を使用します。時間フィールドでは、12時間制の時間に h/hh、24時間制に H/HH、分に m/mm、秒に s/ss を使用します。リテラルテキストはフォーマット文字列内の単一引用符の中に配置されるため、'dd/MM/yyyy' はすでに正しいですが、'dd '<literal>' MM' には埋め込まれた単一引用符のペアが必要になります

対照的に、Delphiの FormatDateTime は、異なるセマンティクスを持つ日の形式に d/dd/ddd/dddd を使用し、月には(M ではなく)m を使用します。FormatDateTime 呼び出しからフォーマット文字列をコピーして DateTime_SetFormat に直接渡すと、出力で月と分のフィールドがおそらく入れ替わります。Delphiのフォーマットから変換するのではなく、Windowsのドキュメントを参照してコントロールのフォーマット文字列を最初から記述してください

地域の設定とグローバルなShortDateFormat

Delphiのグローバルな ShortDateFormat 変数を変更しても、すでに作成されている TDateTimePicker には影響しません。ピッカーは作成時にWindowsから地域の設定を読み取り、その後はDelphi RTLのグローバル変数ではなく、受信したメッセージに従ってレンダリングします。つまり、FormCreateShortDateFormat を設定しても、コントロールの表示は変わりません。ハンドルが存在した後にコントロール独自のフォーマット文字列をオーバーライドするには、DateTime_SetFormat(または新しいDelphiでは Format プロパティ)が必要です

国際的に展開されるアプリケーションの場合、米国外の地域設定を持つマシンでフォーマット文字列をテストしてください。Windowsは空白またはnilのフォーマット文字列をロケールのデフォルトでオーバーライドできますが、明示的なnil以外の文字列は地域設定を完全に上書きします。通常はこれが望ましい動作ですが、これは 'MM/dd/yyyy' のような米国中心のパターンが、慣例として dd/MM/yyyy を期待するユーザーにそのまま表示されることを意味します。日付ピッカーが純粋なデータ入力フィールドではなくユーザー向けである場合は、コントロールをデフォルトのままにして(DateTime_SetFormatnil を渡す)、ユーザーのロケールに自動的に従うようにすることを検討してください

ハンドルの再作成

フォーマット設定は、VCLオブジェクトのフィールドではなく、ウィンドウハンドルに属します。実行時にコントロールの親を変更したり、特定のスタイルプロパティを変更したりしたときにVCLが実行するように、コントロールのハンドルが破棄されて再作成されると、フォーマットはWindowsのデフォルトに戻ります。DateTime_SetFormat を適用する安全な場所は、ハンドル作成後に実行されるハンドラー内です。派生クラスで CreateWnd をオーバーライドしてそこで呼び出すか、ハンドルが有効であることを確認した後にのみ OnEnter または OnShow イベントで適用します。シンプルなフォームの場合は FormCreate で1回設定すれば十分なことが多いですが、フォームで動的なスタイルの切り替えやコントロールの親の変更が行われる場合は信頼性がありません

同じ制約が、新しいDelphiバージョンのVCLの Format プロパティにも適用されます。内部的には同じWindowsメッセージを呼び出し、同じハンドルのライフサイクルに直面します。違いは、VCLのプロパティセッターが呼び出し前にハンドルが割り当てられているかどうかを確認し、CreateWnd でフォーマットを自動的に再適用することです。生の DateTime_SetFormat 呼び出しにはそのような保護がないため、Delphi 5の分岐は、それがいつ実行されるかについてより慎重になる必要があります

回避策の削除

プロジェクトがDelphi 5のサポートを終了すると、条件ブロックは不要な負担になります。{$IFDEF D5} の分岐とスコープされた CommCtrl のインポートを削除し、直接の Format プロパティの割り当てのみを残します。テストしていないコンパイラで予期せず何かが壊れた場合に特定しやすくするため、削除は1回のコミットで行ってください。フォーマット文字列自体は同じままですが、配信メカニズムのみが変更されます