کنترل تاریخ با پشتیبانی تقویم شمسی برای Windows Forms

مقدمه

یکی از مشکلاتی که توسعه دهنده‌های ایرانی باهاش مواجه هستند، تقویم شمسی هست. این مطلب در مورد یک کنترل انتخاب تاریخ و زمان Open-Source با پشتیبانی از تقویم شمسی در محیط Windows Forms در تکنولوژی .NET Framework هست.

تقویم شمسی و .NET

خوشبختانه مایکروسافت در نسخه‌های اولیه NET.، تقویم شمسی رو پشتیبانی کرده و کلاس PersianCalendar رو ارائه داده. با استفاده از این کلاس میشه محاسبات مربوط به تبدیل تاریخ میلادی به شمسی و برعکس رو به خوبی انجام داد. ولی مشکلی که وجود داره این هست که ویندوزهای قبل از ویندوز 10، از تقویم شمسی پشتیبانی نمی‌کنن که این موضوع، با توجه به یکپارچه بودن اغلب محصولات مایکروسافت از جمله .NET Framework با ویندوز، خیلی از مشکلات رو همچنان حل نشده گذاشته. یکی از این مشکلات در .NET Framework، عدم پشتیبانی کنترل مربوط به نمایش و انتخاب تاریخ و زمان، یعنی DateTimePicker هست.
DateTimePicker کنترل کامل و قابل اطمینانی هست ولی برای تقویمی که انتخاب میکنه مسائلی هم داره که شاید خوشایند نباشند:

  • تقویم انتخابی، بر اساس Culture جاری هست که به طور پیش‌فرض تقویم ویندوز رو برای نمایش انتخاب میکنه. با عدم پشتیبانی ویندوز از تقویم شمسی، اعمال تقویم شمسی به Culture جاری کمی دردسر داره. (ولی شدنی هست.)
  • مقدار خالی رو پشتیبانی نمی‌کنه. به این معنی که کنترل حتما باید مقداری رو -که از نوع DateTime هست- داشته باشه.
  • و البته اینکه open-source نیست. (بر خلاف بخشهایی از .NET Framework که open-source هست.)

نمونه‌های مشابه

برای حل مشکلاتی که اشاره کردم، کنترل‌‌های مختلفی توسط توسعه دهنده‌های ایرانی برای WinForms ساخته شده. من خیلی از اون‌‌ها رو بررسی کردم ولی اغلب مشکلات زیادی دارن و حتی اون‌های هم که open-source هستن نیاز به کار زیادی برای بهبود دارن. بعضی مشکلاتی که من تا به حال دیدم این‌ها هستند:

      • اختصاصی برای تقویم شمسی؛ این کنترل‌ها مختص تقویم شمسی ایجاد شدن و قابلیت کار با تقویم غیر از شمسی رو ندارن. بنابراین اگه لازم بشه در نرم افزاری تقویم شمسی در کنار تقویم میلادی مورد استفاده قرار بگیره کاربردی ندارن.
      • فرمت ثابت؛ این کنترل‌ها اغلب برای فقط دریافت تاریخ، اون هم در فرمت ثابتی مثل yyyy/MM/dd طراحی شدن بنابراین نه تنها امکان دریافت زمان در کنار (و یا بدون) تاریخ فراهم نیست، بلکه فقط قادر هستن تاریخ رو با فرمت ثابت دریافت کنن. برای مثال اگه شما بخواهید کاربر مؤلفه سال رو به صورت دو رقمی، نه چهار رقمی وارد کنه، این کنترل‌‌ها توانایی مانور در این زمینه رو ندارند.
      • عدم پشتیبانی صحیح از کیبرد و موس به صورت کامل و صحیح؛
      • واسط کاربری غیر استاندارد؛ همه کنترل‌هایی که من تا به حال دیدم از واسط کاربری غیر استاندارد رنج می‌برن. مشکلاتی از قبیل استفاده از فونت ها و رنگ‌های خاص و غیر قابل تغییر، اندازه‌‌های نامناسب و ثابت و… .

DateTimeSelector

کنترل DateTimeSelector با هدف پشتیبانی کامل از تقویم شمسی و میلادی (و سایر تقویم‌ها، با احتمالا کمی بهبود) نوشته شده و سعی شده که مشکلاتی که در مورد DateTimePicker‌ و همچنین مشکلات نمونه‌های مشابه ایرانی که بهش اشاره کردم رو نداشته باشه. در واقعه، ویژگی‌های این کنترل، نداشتن مشکلاتی هست که بهش اشاره شد:

  • پشتیبانی کامل از تقویم شمسی و میلادی (و سایر تقویم‌ها، با احتمالا کمی بهبود)
  • پشتیبانی از مقدار null (خالی) برای مقدار
  • امکان انتخاب زمان، تاریخ و یا هر دو
  • امکان تغییر فونت و جهت (Direction)
  • امکان تغییر رنگ‌ها
  • طراحی گرافیکی منطبق بر Style ویندوز و برنامه
  • پشتیبانی مناسب از کیبرد و موس*
  • رسم با سرعت بالا بدون Flicker
  • open-source و تحت لیسانس MIT
  • و…

* در زمینه پشتیبانی کامل از موس و کیبرد کمی جای کار داره
چند Screenshot از این کنترل:

DateTimeSelector - Screenshot (1)
شکل 1 – انتخاب تاریخ شمسی
DateTimeSelector - Screenshot (2)
شکل 2 – انتخاب تاریخ و ساعت (شمسی)
DateTimeSelector - Screenshot (3)
شکل 3 – انتخاب تاریخ میلادی با فرمت نمایش دلخواه

دریافت

در حال حاضر، ویراست 1.1.2 این پروژه در Github قرار گرفته که برای بررسی می‌تونید از این لینک ها استفاده کنید:
پروژه در Github
دانلود سورس و یا باینری (DLL)

سوالات متداول (این بخش به مرور بروز رسانی میشه)

با توجه به سوالاتی که مطرح شده دراین قسمت توضیحاتی رو در مورد نحوه استفاده از کنترل قرار میدم تا ابهامات رایج رفع بشه.

چطور این کنترل رو به Toolbox‌ اضافه کنیم؟

برای نمایش کنترل در Toolbox بر روی Toolbox راست کلیک کنید و گزینه Choose Items… رو انتخاب کنید. در پنجره ی Choose Items…، بخش .NET Framework Components گزینه Browse… رو کلیک کنید و فایل DateTimePicker.dll رو انتخاب کنید تا علاوه بر اضافه شدن Reference، کنترل مربوطه به Toolbox اضافه بشه.

چطور تاریخ و تقویم به صورت شمسی در کنترل نمایش داده میشه؟

این کنترل به صورت پیش‌فرض بر اساس Culture مربوط به Thread جاری (که تقویم اون میلادی هست) تاریخ و تقویم رو نمایش میده. برای شمسی کردن تاریخ، بدون توجه به Culture، مشخصه UsePersianFormat رو از پنجره Properties یا از طریق کد برابر True قرار بدید:

dateTimeSelector1.UsePersianFormat = true;

چطور تاریخ رو به صورت میلادی به کنترل انتساب بدیم و یا از کنترل دریافت کنیم؟

برای دریافت تاریخ جاری کنترل و یا انتساب یک تاریخ به کنترل از مشخصه Value استفاده کنید. این مشخصه از نوع DateTime? هست که میتونه مقدار null رو هم که معادل خالی بودن کنترل هست رو دریافت کنه یا برگردونه (توضیحات بیشتر در مورد مقادیر nullable).

// انتساب تاریخ جاری به کنترل
dateTimeSelected1.Value = DateTime.Now;

// دریافت تاریخ انتخاب شده
DateTime? selectedValue = dateTimeSelector1.Value;
if (selectedDate.HasValue) {
   DateTime value = selectedDate.Value;
   // تاریخ انتخاب شده در متغیر بالا قرار گرفته
}
else {
   // تاریخی انتخاب نشده
}

چطور تاریخ رو به صورت شمسی به کنترل انتساب بدیم و یا از کنترل دریافت کنیم؟

برای دریافت تاریخ به صورت شمسی میتونید از مشخصه Text استفاده کنید، در این حالت، تاریخ با فرمت جاری کنترل برگشت داده میشه:

نکته
به دلیل ترجیح استفاده از نوع تاریخ و مشخصه Value، مشخصه Text به طور پیش‌فرض مخفی هست در Editor نمایش داده نمیشه.

string text = dateTimeSelector1.Text;
MessageBox.Show(text);

در صورتی که بخواهید با فرمت خاصی تاریخ رو دریافت کنید میتونید از متد GetText استفاده کنید:

// دریافت تاریخ به صورت سال و ماه
string text = dateTimeSelector1.GetText("yyyy/MM");
MessageBox.Show(text);

برای انتساب تاریخ به صورت شمسی به کنترل میتونید از مشخصه Text استفاده کنید. کنترل سعی میکنه که تاریخ رو Parse کنه و به تاریخ میلادی تبدیل کنه، در صورتی که این عمل موقفیت آمیز نباشه مقدار کنترل خالی (null) میشه. بجای استفاده از این مشخصه میتونید خودتون بر اساس فرمتی که تاریخ متنی مورد تظرتون داره، با کمک کلاس PersianCalendar اون رو به تاریخ میلادی تبدیل کنید و به مشخصه Value انتساب بدین. احتمال خطا در روش دوم کمتره.

dateTimeSelector1.UsePersianFormat = true;
dateTimeSelector1.Text = "1395/10/12";

چطور تاریخ و زمان رو با فرمت های مختلف در کنترل نمایش بدیم؟

همونطور که اشاره کردم و در تصاویر هم مشخصه، کنترل میتونه تاریخ و زمان رو با فرمت دلخواه نمایش بده و ذخیره کنه. برای نمایش در یکی از فرمت‌های پیش‌فرض از مشخصه Format استفاده کنید:

// نمایش زمان بدون تاریخ
dateTimeSelector1.Format = Atf.UI.DateTimeSelectorFormat.Time;

برای نمایش در فرمت دلخواه، مشخصه Format رو برابر Custom قرار بدین و مشخصه CustomFormat رو مقدار دهی کنید:

// نمایش تاریخ و زمان
dateTimeSelector1.Format = Atf.UI.DateTimeSelectorFormat.Custom;
dateTimeSelector1.CustomFormat = "yyyy/MM/dd hh:mm:sstt";

برای آشنایی بیشتر با مولفه‌های تاریخ سفارشی اینجا رو مطالعه کنید. این کنترل از بیشتر این مولفه ها پشتیبانی میکنه.

چطور از تغییر مقدار کنترل مطلع بشیم و یا چطور کنترل رو Bind کنیم؟

رویداد ValueChanged زمانی که مقدار جاری کنترل تغییر میکنه نمایش داده میشه. همینطور برای Bind کردن مقدار کنترل میتونید از مشخصه Value استفاده کنید.

چطور بخش تقویم کنترل رو سفارشی کنیم؟

با استفاده از مشخصه های CalendarBackColor ،CalendarForeColor ،CalendarTitleBackColor ،CalendarTitleForeColor و CalendarTrailingForeColor در پنجره Properties یا از طریق کد میتونید رنگ بخش های مختلف تقویم رو تغییر بدین. به صورت پیش‌فرض این رنگ ها مطابق با تم ویندوز هستن. برای راست یه چپ کردن تقویم هم از مشخصه CalendarRightToLeft استفاده کنید.