在 Delphi 5 與 Delphi 6 之間的某個版本,TDateTimePicker 新增了 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}
請將 CommCtrl 加進包含 Delphi 5 分支的 unit 的 uses 子句中。不需要無條件地加入它;如果您偏好將相依性限定在範圍內,可將該 uses 項目包裝在相同的條件式中:
uses
...,
{$IFDEF D5}
CommCtrl,
{$ENDIF}
...;
格式字串語法
傳遞給 DateTime_SetFormat 的格式字串遵循的是 Windows DateTimePicker 格式,而非 Delphi 的 FormatDateTime 符記。兩者看起來很像,但並不能互換使用。Windows 使用 d 代表月份中的日(不補零),dd 代表補零的日,M 代表月份數字,MM 代表補零的月,yy 代表兩位數年份,yyyy 代表四位數年份。時間欄位使用 h/hh 表示 12 小時制時數,H/HH 表示 24 小時制,m/mm 表示分鐘,s/ss 表示秒。字面文字要放在格式字串內的單引號中,因此 'dd/MM/yyyy' 已經是正確的了,但如果是 'dd '<literal>' MM' 則需要內嵌的成對單引號
相比之下,Delphi 的 FormatDateTime 使用 d/dd/ddd/dddd 表示具有不同語意的日形式,並使用 m(而非 M)表示月份。如果您從 FormatDateTime 呼叫中複製格式字串並直接交給 DateTime_SetFormat,輸出中的月份與分鐘欄位大概會被對調。請直接根據 Windows 文件來撰寫該控制項的格式字串,而不要從 Delphi 格式進行轉換
地區設定與全域 ShortDateFormat
變更 Delphi 全域的 ShortDateFormat 變數,並不會影響已經建立的 TDateTimePicker。該選擇器會在建立時從 Windows 讀取地區設定,之後則根據它收到的訊息來渲染,而非根據 Delphi RTL 的全域變數。這表示在 FormCreate 中設定 ShortDateFormat 不會改變控制項的顯示內容。在取得控制代碼之後,您需要 DateTime_SetFormat(或較新 Delphi 上的 Format 屬性)來覆蓋控制項專屬的格式字串
對於跨國部署的應用程式,請在採用非美國地區設定的機器上測試該格式字串。Windows 可以使用地區預設值來覆寫空白或 nil 的格式字串,但是明確的非 nil 字串則會完全取代該地區設定。這通常是您想要的結果,但這意味著像 'MM/dd/yyyy' 這種以美國為中心的模式,對於預期慣例為 dd/MM/yyyy 的使用者,將會原封不動地顯示。如果日期選擇器是面向使用者,而不只是純粹的資料輸入欄位,請考慮讓控制項採用預設值(傳遞 nil 給 DateTime_SetFormat),這樣它就會自動跟隨使用者的地區設定
重建控制代碼 (Handle recreation)
格式設定隸屬於視窗控制代碼,而非任何 VCL 物件欄位。如果控制項的控制代碼被銷毀並重建(當您在執行階段變更控制項的父容器或特定樣式屬性時,VCL 可能會這樣做),格式就會回復到 Windows 的預設值。套用 DateTime_SetFormat 的安全位置,是在控制代碼建立後執行的處理常式中:在衍生類別中覆寫 CreateWnd 並在那裡呼叫它,或者只有在確認控制代碼是活動狀態後,才在 OnEnter 或 OnShow 事件中套用它。對於簡單的表單,只在 FormCreate 中設定一次通常就足夠了,但如果表單會動態切換樣式或重新指定父容器,這就不太可靠了
這項限制同樣適用於較新 Delphi 版本上的 VCL Format 屬性;在內部,它會呼叫同一個 Windows 訊息,並面臨相同的控制代碼生命週期。差別在於 VCL 屬性設定器 (setter) 會在呼叫前檢查控制代碼是否已配置,並會在 CreateWnd 上自動重新套用該格式。原生的 DateTime_SetFormat 呼叫並沒有這樣的保護機制,因此 Delphi 5 的分支需要更審慎地決定它執行的時機
移除應急修補
當專案不再支援 Delphi 5 時,該條件式區塊就成了累贅。請移除 {$IFDEF D5} 分支和限定範圍的 CommCtrl 匯入,只留下直接的 Format 屬性指派。將這個移除動作保留在單一的 commit 中,這樣如果在未測試的編譯器上發生預期外的錯誤時,就能輕易辨識出來。格式字串本身保持不變;改變的只有傳遞機制