مقاله فنی

فرمت TDateTimePicker در Delphi 5: DateTime_SetFormat

کنترل TDateTimePicker در نسخه‌ای بین Delphi 5 و Delphi 6 ویژگی Format را به دست آورد. مرز دقیق این نسخه به ندرت به گونه‌ای مستند شده که بتوانید به آن اعتماد کنید، و وقتی با آن مواجه می‌شوید، کامپایلر به سادگی به شما می‌گوید که این شناسه وجود ندارد. خطی که کامپایل می‌شود و روی هر دلفی جدیدتر که دارید کار می‌کند، بسته به نحوه ساختار پروژه، در دلفی قدیمی بی‌صدا یا به طور کامل از کار می‌افتد

کنترل زیربنایی همان کنترل مشترک (common control) ویندوز با نام DATETIMEPICK_CLASS از comctl32.dll است و همیشه یک رشته فرمت را از طریق پیام DTM_SETFORMAT پذیرفته است. VCL در دلفی این پیام را در ویژگی Format در نسخه‌های جدیدتر پوشش می‌دهد (wrap). در دلفی 5، شما این پیام را خودتان از طریق پوشش ماکرو DateTime_SetFormat که در CommCtrl اعلان شده است ارسال می‌کنید

الگوی کامپایل شرطی (The conditional compilation pattern)

رویکرد استاندارد، یک بلوک شرطی کامپایلر است که بر پایه یک نماد (symbol) نسخه کلید خورده است. دلفی 5 مقدار VER130 را تعریف می‌کند؛ شما می‌توانید مستقیماً در برابر آن آزمایش کنید یا نماد D5 خود را در گزینه‌های پروژه تعریف کنید تا خوانایی برچسب شاخه (branch label) در سراسر کد منبع بیشتر شود. در هر صورت، شاخه مربوط به Delphi 5 از طریق DateTime_SetFormat با هندل کنترل (control handle) فراخوانی می‌شود؛ هر شاخه دیگر ویژگی را مقداردهی می‌کند:

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

شما CommCtrl را به بخش uses در یونیتی (unit) که شاخه Delphi 5 را در خود دارد اضافه کنید. نیازی به اضافه کردن بدون شرط آن نیست؛ اگر ترجیح می‌دهید وابستگی را محدود (scoped) نگه دارید، ورودی uses را در همان شرط بپیچید:

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

سینتکس رشته فرمت

رشته فرمتی که به DateTime_SetFormat پاس داده می‌شود، از فرمت انتخاب‌گر تاریخ و زمان (date-time picker) ویندوز پیروی می‌کند، نه از توکن‌های FormatDateTime در دلفی. این دو شبیه هم هستند اما قابل تعویض نیستند. ویندوز از d برای روز در ماه (بدون صفر اول)، dd برای روز با صفر اول، M برای شماره ماه، MM برای ماه با صفر اول، yy برای سال دو رقمی و yyyy برای سال چهار رقمی استفاده می‌کند. فیلدهای زمانی از h/hh برای ساعات 12 ساعته، H/HH برای ساعات 24 ساعته، m/mm برای دقیقه‌ها، s/ss برای ثانیه‌ها استفاده می‌کنند. متن عینی (Literal text) در داخل کوتیشن تکی در رشته فرمت قرار می‌گیرد، بنابراین 'dd/MM/yyyy' از قبل درست است، اما 'dd '<literal>' MM' به زوج کوتیشن‌های تکی تعبیه‌شده نیاز دارد

در مقابل، FormatDateTime در دلفی از d/dd/ddd/dddd برای اشکال روز با معانی متفاوت و از m برای ماه (نه M) استفاده می‌کند. اگر یک رشته فرمت را از فراخوانی FormatDateTime کپی کرده و آن را مستقیماً به DateTime_SetFormat پاس دهید، احتمالاً فیلدهای ماه و دقیقه در خروجی جابه‌جا خواهند شد. رشته فرمت کنترل را از پایه و با استفاده از مستندات ویندوز بنویسید، نه اینکه آن را از یک فرمت دلفی تبدیل کنید

تنظیمات منطقه‌ای و متغیر سراسری ShortDateFormat

تغییر متغیر سراسری ShortDateFormat در دلفی تاثیری بر یک TDateTimePicker که قبلاً ایجاد شده است نمی‌گذارد. انتخاب‌گر، تنظیمات منطقه‌ای (regional settings) را از ویندوز در زمان ایجاد می‌خواند و پس از آن بر اساس پیام‌هایی که دریافت می‌کند رندر می‌شود، نه بر اساس متغیرهای سراسری RTL دلفی. این بدان معناست که تنظیم ShortDateFormat در FormCreate آنچه را که کنترل نمایش می‌دهد تغییر نخواهد داد. شما به DateTime_SetFormat (یا ویژگی Format در دلفی‌های جدیدتر) نیاز دارید تا رشته فرمت کنترل را پس از ایجاد هندل باطل (override) کند

برای برنامه‌هایی که در سطح بین‌المللی مستقر می‌شوند، رشته فرمت را در ماشینی با تنظیمات منطقه‌ای غیرآمریکایی (non-US) آزمایش کنید. ویندوز می‌تواند یک رشته فرمت خالی یا null را با پیش‌فرض منطقه (locale default) باطل کند، اما یک رشته غیر null صریح به طور کامل تنظیمات منطقه‌ای را جایگزین می‌کند. این معمولاً همان چیزی است که شما می‌خواهید، اما به این معنی است که الگویی با محوریت آمریکا مانند 'MM/dd/yyyy' به همان شکل برای کاربرانی نمایش داده می‌شود که طبق قرارداد انتظار dd/MM/yyyy را دارند. اگر انتخاب‌گر تاریخ، به جای یک فیلد ورود داده خالص، رو به کاربر است، اجازه دهید تا پیش‌فرض کنترل اجرا شود (عبور دادن nil به DateTime_SetFormat) تا به طور خودکار از منطقه کاربر پیروی کند

ایجاد مجدد هندل (Handle recreation)

تنظیم فرمت به هندل پنجره تعلق دارد، نه به هیچ فیلد شی VCL. اگر هندل کنترل از بین رفته و دوباره ایجاد شود، که VCL می‌تواند آن را زمانی که یک کنترل را بازوالد (reparent) می‌کنید یا ویژگی‌های استایل خاصی را در زمان اجرا تغییر می‌دهید انجام دهد، فرمت به پیش‌فرض ویندوز بازمی‌گردد. محل امن برای اعمال DateTime_SetFormat در یک کنترل‌کننده رویداد (handler) است که پس از ایجاد هندل اجرا می‌شود: CreateWnd را در یک کلاس وارث بازنویسی کنید و آن را در آنجا فراخوانی کنید، یا آن را در رویداد OnEnter یا OnShow تنها پس از تایید زنده بودن هندل اعمال کنید. تنظیم آن یک بار در FormCreate برای فرم‌های ساده اغلب کافی است، اما اگر فرم کار تعویض پویای استایل یا بازوالد کردن کنترل را انجام دهد قابل اعتماد نیست

همین محدودیت در مورد ویژگی VCL Format در نسخه‌های جدیدتر دلفی نیز اعمال می‌شود؛ در داخل همان پیام ویندوز را فراخوانی می‌کند و با همان چرخه عمر هندل مواجه است. تفاوت در این است که مقداردهنده (setter) ویژگی VCL قبل از فراخوانی بررسی می‌کند که آیا هندل تخصیص یافته است یا خیر، و به طور خودکار فرمت را در CreateWnd مجدداً اعمال می‌کند. فراخوانی خام DateTime_SetFormat چنین محافظتی ندارد، بنابراین شاخه دلفی 5 باید در مورد زمان اجرای خود دقیق‌تر باشد

حذف راه‌حل

هنگامی که پروژه‌ای پشتیبانی از Delphi 5 را کنار می‌گذارد، بلوک شرطی به بار اضافی تبدیل می‌شود. شاخه {$IFDEF D5} و وارد کردن اسکوپ‌دار (scoped) CommCtrl را بردارید و تنها انتساب مستقیم ویژگی Format را باقی بگذارید. این حذف را در یک کامیت واحد نگه دارید تا اگر چیزی در کامپایلری که آزمایش نکرده‌اید به طور غیرمنتظره‌ای خراب شد، شناسایی آن آسان باشد. رشته فرمت خود دست‌نخورده باقی می‌ماند؛ فقط مکانیسم تحویل تغییر می‌کند