TDateTimePicker đã có được thuộc tính Format ở đâu đó giữa phiên bản Delphi 5 và Delphi 6. Ranh giới phiên bản chính xác hiếm khi được ghi nhận bằng văn bản để bạn có thể tin tưởng, và khi bạn gặp phải nó, trình biên dịch chỉ đơn giản báo cho bạn biết rằng mã định danh (identifier) không tồn tại. Dòng mã biên dịch và hoạt động trơn tru trên mọi Delphi mới hơn của bạn bị lỗi âm thầm hoặc hoàn toàn trên phiên bản cũ, tùy thuộc vào cách dự án được cấu trúc
Điều khiển cơ sở là điều khiển chung của Windows DATETIMEPICK_CLASS từ comctl32.dll, và nó luôn chấp nhận chuỗi định dạng thông qua thông báo DTM_SETFORMAT. VCL của Delphi bọc thông báo đó trong thuộc tính Format trên các phiên bản mới hơn. Trên Delphi 5, bạn tự gửi thông báo qua trình bọc macro DateTime_SetFormat được khai báo trong CommCtrl
Mẫu biên dịch có điều kiện (conditional compilation pattern)
Cách tiếp cận tiêu chuẩn là một khối biên dịch có điều kiện dựa trên ký hiệu phiên bản. Delphi 5 xác định VER130; bạn có thể kiểm tra trực tiếp đối với nó hoặc xác định ký hiệu D5 của riêng mình trong các tùy chọn dự án để làm cho nhãn nhánh trở nên dễ đọc hơn trong toàn bộ cơ sở mã. Dù bằng cách nào, nhánh Delphi 5 gọi DateTime_SetFormat với control handle; mọi nhánh khác sẽ gán thuộc tính:
{$IFDEF D5}
DateTime_SetFormat(DateTimePicker1.Handle, PChar('MM/dd/yyyy'));
{$ELSE}
DateTimePicker1.Format := 'MM/dd/yyyy';
{$ENDIF}
Thêm CommCtrl vào mệnh đề uses đối với unit chứa nhánh Delphi 5. Không cần phải thêm nó một cách vô điều kiện; nếu bạn muốn giữ giới hạn sự phụ thuộc trong phạm vi (scoped), hãy bọc phần khai báo uses trong cùng một điều kiện:
uses
...,
{$IFDEF D5}
CommCtrl,
{$ENDIF}
...;
Cú pháp chuỗi định dạng
Chuỗi định dạng được chuyển tới DateTime_SetFormat tuân theo định dạng bộ chọn ngày giờ của Windows, chứ không phải các mã thông báo FormatDateTime của Delphi. Cả hai trông tương tự nhưng không thể hoán đổi cho nhau. Windows sử dụng d cho ngày trong tháng (không có số 0 ở đầu), dd cho ngày được đệm số 0, M cho số tháng, MM cho tháng được đệm số 0, yy cho năm hai chữ số, và yyyy cho năm bốn chữ số. Các trường thời gian sử dụng h/hh cho giờ theo hệ 12 tiếng, H/HH cho hệ 24 tiếng, m/mm cho phút, s/ss cho giây. Chữ văn bản thuần túy (literal text) đi bên trong cặp dấu nháy đơn bên trong chuỗi định dạng, vì vậy 'dd/MM/yyyy' đã là chính xác, nhưng 'dd '<literal>' MM' sẽ yêu cầu các cặp dấu nháy đơn lồng nhau
Ngược lại, FormatDateTime của Delphi sử dụng d/dd/ddd/dddd cho các biểu mẫu ngày với các ngữ nghĩa khác nhau, và m cho tháng (không phải M). Nếu bạn sao chép một chuỗi định dạng từ một cuộc gọi FormatDateTime và giao trực tiếp cho DateTime_SetFormat, các trường tháng và phút có thể sẽ bị hoán đổi trong kết quả đầu ra. Hãy viết chuỗi định dạng của điều khiển từ đầu dựa trên tài liệu Windows thay vì chuyển đổi từ định dạng Delphi
Cài đặt khu vực và ShortDateFormat toàn cục
Việc thay đổi biến ShortDateFormat toàn cục (global) của Delphi không ảnh hưởng đến một TDateTimePicker đã được tạo. Trình chọn sẽ đọc cài đặt khu vực từ Windows tại thời điểm tạo và sau đó hiển thị theo các thông báo nhận được, chứ không phải các biến toàn cục (globals) của Delphi RTL. Điều đó có nghĩa là thiết lập ShortDateFormat trong FormCreate sẽ không thay đổi những gì điều khiển hiển thị. Bạn cần DateTime_SetFormat (hoặc thuộc tính Format trên các bản Delphi mới hơn) để ghi đè chuỗi định dạng của chính điều khiển sau khi handle đó tồn tại
Đối với các ứng dụng được triển khai trên phạm vi quốc tế, hãy kiểm tra chuỗi định dạng trên một máy tính có cài đặt khu vực không phải Hoa Kỳ. Windows có thể ghi đè một chuỗi định dạng trống hoặc vô hiệu (nil) bằng cấu hình ngôn ngữ (locale) mặc định, nhưng một chuỗi không vô hiệu rõ ràng sẽ thay thế hoàn toàn cài đặt khu vực. Đó thường là những gì bạn muốn, nhưng điều đó có nghĩa là một mẫu tập trung vào Hoa Kỳ như 'MM/dd/yyyy' sẽ xuất hiện nguyên trạng đối với người dùng mong đợi định dạng theo quy ước là dd/MM/yyyy. Nếu trình chọn ngày hướng đến người dùng (user-facing) thay vì là một trường nhập dữ liệu thuần túy, hãy cân nhắc để cấu hình điều khiển ở chế độ mặc định (truyền nil vào DateTime_SetFormat) để nó tuân theo ngôn ngữ mặc định của người dùng một cách tự động
Tạo lại Handle
Thiết lập định dạng thuộc về window handle, chứ không phải bất kỳ trường đối tượng VCL nào. Nếu handle của điều khiển bị hủy và được tạo lại, điều mà VCL có thể làm khi bạn gắn lại (reparent) một điều khiển hoặc thay đổi một số thuộc tính kiểu nhất định trong thời gian chạy (runtime), định dạng sẽ hoàn nguyên về mặc định của Windows. Nơi an toàn để áp dụng DateTime_SetFormat là trong một trình xử lý chạy sau khi tạo handle: hãy ghi đè CreateWnd trong một lớp con kế thừa (descendant) và gọi nó ở đó, hoặc áp dụng nó trong sự kiện OnEnter hoặc OnShow chỉ sau khi xác nhận handle đang hoạt động (live). Thiết lập nó một lần trong FormCreate thường là đủ đối với các biểu mẫu đơn giản, nhưng sẽ không đáng tin cậy nếu biểu mẫu thực hiện chuyển đổi kiểu động (dynamic style switching) hoặc gắn lại (reparenting) điều khiển
Ràng buộc tương tự cũng áp dụng cho thuộc tính VCL Format trên các phiên bản Delphi mới hơn; về mặt nội bộ nó gọi cùng một thông báo Windows và đối mặt với cùng một vòng đời handle. Sự khác biệt là setter thuộc tính của VCL kiểm tra xem handle đã được phân bổ trước khi gọi tới, và tự động áp dụng lại định dạng trên CreateWnd. Gọi trực tiếp DateTime_SetFormat không có lớp bảo vệ như vậy, nên nhánh Delphi 5 cần phải chủ ý kỹ hơn về thời điểm nó chạy
Loại bỏ cách khắc phục
Khi một dự án ngừng hỗ trợ Delphi 5, khối điều kiện trở thành gánh nặng chết. Hãy loại bỏ nhánh {$IFDEF D5} và lệnh nhập (import) có giới hạn phạm vi CommCtrl, chỉ để lại phép gán thuộc tính trực tiếp Format. Giữ việc gỡ bỏ trong một cam kết (commit) duy nhất để dễ dàng xác định nếu có điều gì đó không mong đợi bị phá vỡ trên trình biên dịch bạn đã không kiểm tra. Bản thân chuỗi định dạng sẽ được giữ nguyên; chỉ cơ chế phân phối bị thay đổi