کنترل تاریخ با پشتیبانی تقویم شمسی برای 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 استفاده کنید.

آشنایی با Layout در Windows Forms

مقدمه

هر چند برنامه سازی برای محیط ویندوز به رونق قبل نیست و وب بخشی زیادی از بازار رو تصاحب کرده، ولی همچنان توسعه دهنده‌های زیادی رو به خودش مشغول کرده و در این حوزه یکی از پس استفاده ترین روش ها همچنان استفاده از Windows Forms از زیر مجوعه .NET Framework هست.در این پست درباره چگونگی Layout و امکانات ارائه شده در WinForms می‌نویسم.اگه WinForms کار کرده باشید احتمالا با بخشی از این مطالب آشنایی دارین اما ممکنه برخی از مطالب رو اصلا در موردش نشنیده باشید.در این مطلب این موارد نوشته میشه:

  1. Layout
  2. Auto Scaling
  3. Anchoring
  4. Docking
  5. Splitting
  6. Grouping
  7. Layout Controls
  8. Custom Layout

Layout

اگه تاالان یک فرم یا واسط گرافیکی طراحی کرده باشین حتما درگیر Layout بودین حتی اگه چیزی در موردش نشنیدید. به طور کلی در حوزه کامپیوتر، Layout به پروسه محاسبه اندازه و محل قرار گیری تعدادی Object یا شی بر اساس تعدادی از محدودیت ها گفته میشه.در WinForms این تعریف رو میشه به محاسبه اندازه و محل قرار گیری کنترل ها روی فرم یا Container دیگه تفسیر کرد.

با آشنایی با امکاناتی که WinForms در اختیار ما گذاشته میتونیم نحوه قرار گیری و اندازه کنترل ها رو بر اساس نیازمون تنظیم کنیم به طوری که واسط طراحی شده در سیستم های مختلف با فونت ها و تنظیمات DPI مختلف بتونه به درستی نمایش داده بشه و یا با تغییر اندازه فرم‌ها محل قرارگیری کنترل ها به صورت مناسب تغییر کنه.در WinForms عمل Layout توسط Layout Engine های پیشفرض تعریف شده برای کنترل ها انجام میشه و البته شما می‌تونید در صورت نیاز Layout Engine‌ سفارشی ایجاد کنید.

امکانات

احتمالا با مفاهیمی مثل Docking و Anchoring آشنایی دارید. این دو، بخشی از امکانات و نه تمام امکانات ارائه شده هست.این امکانات رو میشه به صورت زیر معرفی کرد:

Auto Scaling

قبل از معرفی این ویژگی، اول در مورد توضیحاتی میدم.کلاس ContainerControl یک زیرکلاس از کلاس ScrollableControl و در ادامه Control هست که ویژگی هایی که مربوط که نگه داری کنترل‌های دیگه هست رو داره و کلاس های زیر در WinForms ازین کلاس ارث بری کردن:

  1. Form
  2. PropertyGrid
  3. SplitContainer
  4. ToolStripContainer
  5. ToolStripPanel
  6. UpDownBase
  7. UserControl

بنابراین همه فرم ها و UserControl هایی که طراحی میکنید ContainerControl هستن.
با این اوصاف، امکان AutoScaling برای تطبیق ContainerControl طراحی شده در DPI ها و یا فونت های متفاوت به صورت چند Property و Method در کلاس ContainerControl ارائه شده که مهم ترین اون ها مشخصه AutoScaleMode هست.با استفاده از این مشخصه شما می‌تونید تعیین کنید که اندازه و محل قرار گیری کنترل ها در یک فرم، پنل یا هر Container Control دیگه با تغییر DPI مانیتور و یا فونت چطور تغییر کنه.مقادیر قابل انتخاب عبارتند از:

None
Scaling انجام نمی‌شود.
Font
‌Scaling بر اساس تغییر اندازه فونت انجام می‌شود.
Dpi
Scaling‌ بر اساس تغییر DPI انجام می‌شود.
Inherit
Scaling از کنترل پدر ارث بری میشود.

مقداری که موقع ایجاد ContainerControl ی Built-in به این مشخصه داده میشه Font هست، بنابراین تعجب نکنید اگه با تغییر Font یک فرم یا UserControl می‌بینید که اندازه خودش و کنترل های داخلش تغییر می‌کنه!

تاثیر AutoScaleMode با تغییر Font
شکل 1 – تاثیر AutoScaleMode با تغییر Font

مقدار DPI زمانی کاربر داره که لازمه محل و اندازه کنترل‌ها فقط با تغییر DPI منطبق باشه البته در Windows Vista به بعد تا زمانی که برنامه شما اعلام نکنه که DPI Aware هست استفاده ازین گزینه تاثیری نداره.در این مورد مطالب مختلفی برای گفتن هست که برای طولانی نشدن مطلب ازش میگذرم.مقدار None هم باعث میشه در صورت تغییر Font یا DPI هیچ تغییری صورت نگیره.بهتره از مقدار پیشفرض در این مورد استفاده کنید.

Anchoring

Anchoring‌ برای اتصال یک کنترل به یک یا چند گوشه از کنترل در بر دارنده‌ش (Container) استفاده میشه و زمانی بکار میره که باید اندازه یا محل یک کنترل با تغییر اندازه Container اون، تغییر کنه که این ویژگی با استفاده از مشخصه Anchor انجام میشه.این مشخصه یک یا چند تا از این مقادیر میتونه باشه:

  1. Left
  2. Top
  3. Right
  4. Bottom

که مقدار پیشفرض Left|Top هست که مشخص میکنه فاصله ضلع سمت چپ کنترل مورد نظر با سمت چپ کنترل در بر دارنده همیشه ثابت هست (صرف نظر از Scaling) و همین موضوع در مورد ضلع بالایی هم صدق میکنه.تصویر زیر، تغییر اندازه فرم با تنظیمات پیشفرض برای کنترل های TextBox و Button رو نشون میده:

تاثیر Anchor پیشفرض با تغییر اندازه فرم
شکل 2 – تاثیر Anchor پیشفرض با تغییر اندازه فرم

همونطور که می‌بینید، با تغییر اندازه فرم، فاصله ضلع سمت چپ و بالای کنترل‌ها با ضلع سمت چپ و بالای فرم تغییری نکرده چونه مقدار Anchoer پیشفرض اون‌ها Left|Top هست ولی می‌بینید که فاصله ضلع سمت راست و پایین با تغییر اندازه تغییر کرده.تا اینجا کار خاصی انجام نشده و چیزی هست که به صورت پیشفرض انتظار میره، ولی گاهی لازمه که کنترل‌ها نسبت به این تغییر اندازه واکنش نشون بدن.برای مثال، در تصویر بالا، اگه عرض TextBox با تغییر اندازه فرم تغییر کنه در صورت عریض تر شدن فرم TextBox هم عریض تر میشه و از فضا استفاده بهتری میشه.برای اعمال این رفتار، مشخصه Anchor مربوط به TextBox‌ رو برابر با Left|Right|Top قرار می‌دیم.این تغییر هم از طریق Designer و هم کد قابل انجام هست.نتیجه به این صورت خواهد بود:
تاثیر Anchor با تغییر اندازه فرم
شکل 3 – تاثیر Anchor با تغییر اندازه فرم

Docking

برخی مواقع لازم هست که کنترل ها از حداکثر فضای موجود در جهت افقی، عمودی یا هر دو استفاده کنند و نمیشه -یا سخت هست که- با استفاده از Anchoring به نتیجه دلخواه رسید و به جای اون میشه از امکان Docking و مشخصه Dock استفاده کرد. مشخصه Dock یکی از این مقادیر میتونه باشه:

  1. None
  2. Left
  3. Top
  4. Right
  5. Bottom
  6. Fill

و مقدار پیشفرض برای کنترل های مختلف متفاوت هست.Docking یک کنترل در یکی از مقادیر Left، Top، Right و Bottom به این معنی هست که اون کنترل، در منتهی علیه همون سمت از کنترل در بر دارنده قرار میگیره و از حداکثر فضا در جهت مخالف استفاده میکنه.برای مثال، مقدار Top باعث میشه کنترل مورد نظر، در بالای کنترل در بر دارنده قرار بگیره و از حداکثر فضای افقی استفاده کنه. شاید رایج ترین مثال، کنترل MenuStrip باشه.

مشخصه Dock این کنترل به صورت پیشفرض برابر Top هست.
البته شاید با استفاده از Anchoring هم بشه یک چنین رفتاری رو ایجاد کرد ولی راه صحیح استفاده از Dock هست. اینکه در هر مورد از چه روشی استفاده بشه با تجربه به راحتی به دست میاد.
مقدار دیگه ای که مشخصه Dock میتونه بگیره Fill هست. این مقدار باعث میشه که کنترل مورد نظر از حداکثر فضای موجود در در دو جهت افقی و عمودی استفاده کنه. برای مثال، طراحی زیر رو در نظر بگیرین:

Docking در WinForms
شکل 4 – Dock

همونطور که در شکل بالا مشخص هست یک MenuStrip قبلی با Dock پیشفرض و یک ListBox بدون مشخصه Dock ایجاد شدن.ListBox از حداکثر فضای موجود در هیچ کدوم از جهت ها استفاده نکرده. ما می‌خواهیم از فضای موجود در دو جهت استفاده کنه، فضای فرم رو پر کنه و در صورت تغییر اندازه فرم با اون هماهنگ باشه.بنابراین مشخصه Dock اون برابر Fill قرار دادیم.

نکته

  1. همه کنترل ها در همه جهت ها قابل رشد نیستن. برای مثال، ارتفاع کنترل TextBox‌ در صورت تک خطی بودن بر اساس Font اون تعیین میشه و با تغییر مشخصه Dock تغییری نمی‌کنه. در صورت که کنترل سفارشی درست می‌کنید میتونید با override کردن متد GetPrefrredSize اندازه دلخواه رو برای کنترل تعیین کنید.
  2. در صورت تداخل بین کنترل‌های فرمی که Dock شدن و یا در صورتی که در یک کنترل Container‌ مثل فرم، به بیش از یک کنترل، یک مشخصه Dock بدین، مثلا به دو کنترل مشخصه مقدار Bottom بدین، با استفاده از متدهای BringToFront و SendToBack (از طریق کد یا راست کلیک روی کنترل در محیط Designer) می‌تونید ترتیب قرار گیری کنترل ها در پایین Container رو تغیر بدید.

Splitting

زمانی که از Docking استفاده میشه، میتونید با استفاده از Splitting برای کاربر امکان تغییر اندازه کنترل‌های Dock شده رو فراهم کنید.طراحی زیر رو در نظر بگیرید:

تاثیر Dock با تغییر اندازه فرم
شکل 5 – Dock با تغییر اندازه فرم

در این طرح، به طرح قبلی یک ListBox با مشخصه Dock با مقدار Right اضافه کردیم تا در سمت راست فرم قرار بگیره. همونطور که می‌بینید، با تغییر اندازه فرم، ListBox سمت چپ با مشخصه Dock با مقدار Fill، فضای ایجاد شده رو در اختیار می‌گیره ولی ListBox سمت راست تغییر اندازه نمیده. در صورتی که بخوایم به کاربر امکان تغییر عرض ListBox راستی رو بدیم، میتونم از کنترل Splitter استفاده کنیم. برای این کار یک کنترل Splitter با مقدار Right برای Dock به فرم اضافه می‌کنیم.
همونطور که می‌بینید کنترل مورد نظر بین دو کنترل ListBox قرار گرفته. ممکنه لازم بشه تا با استفاده از تغییر Index کنترل‌ها (با راست کلیک روی اون‌ها و استفاده از Bring to Front یا Send to Back) کنترل‌ها در جای مناسب قرار بدید.کاربر میتونه با آوردن اشاره گر موس روی کنترل Splitter فضای ختصاص داده شده به کنترل‌ها رو تغییر بده.
WinForms_Splitter_Design
شکل 6 – Splitter برای تغییر اندازه کنترل‌ها توسط کاربر

استفاده از Splitter برای تغییر اندازه کنترل‌ها توسط کاربر
شکل 7 – استفاده از Splitter برای تغییر اندازه کنترل‌ها توسط کاربر

Grouping

گاهی لازمه برای رسیدن به Layout مورد نظر، کنترل‌های فرم رو دسته بندی کنید. برای این کار می‌تونید از کنترل‌هایی مثل GroupBox، Panel و یا TabControl استفاده کنید. این کنترل‌ها، میتونن تعدادی کنترل رو در خودشون نگه دارن. (همونطور که فرم کنترل‌های دیگه رو نگه میداره.) برای مثال، ما لازم داریم که بالای لیستی که در طرح قبل همه فضای فرم رو با Fill شدن گرفته بود، تعدادی TextBox‌ و یک Buttonاضافه کنیم. از اونجایی که ListBox‌ رو فرم Dock هست و در حالت عادی از همه فضا استفاده میکنه، نمیتونیم TextBox ها و Button رو مستقیما به فرم اضافه کنیم چون ListBox روی اونها قرار میگیره. برای این کار، از یک Panel با مقدار Dock برابر Top استفاده میکنیم و کنترل‌ها رو روی اون میگذاریم.

استفاده از پنل در گروه بندی کنترل ها
شکل 8 – Grouping

Layout Controls

دو کنترل پر کاربر ارائه شده در WinForms برای ایجاد Layout خاص، کنترل‌های FlowLayoutPanel و TableLayout‌Panel هستن. این دو کنترل از LayoutEngine های متفاوتی در مقایسه با سایر کنترل‌های ارائه شده استفاده می‌کنن.
با استفاده از FlowLayoutPanel می‌تونید تعدادی کنترل رو به صورت جاری در کنار همدیگه قرار بدید. محل قرار گیری کنترل‌ها بر اساس اندازه کنترل در بر دارنده (FlowLayoutPanel) و اندازه سایر کنترل‌ها تغییر میکنه.
با استفاده از TableLayout‌Panel می‌تونید کنترل‌ها رو صورت جدول مانند کنار همدیگه قرار بدین به طوری که موقع تغییر اندازه فرم به صورت مناسب تغییر اندازه بدن. با استفاده از این کنترل کمتر درگیر نظم کنترل‌ها و تراز کردن اون‌ها می‌شید. در مورد این کنترل‌ها توضیح زیادی نمی‌دم اما اگه از اون‌ها در طراحی استفاده نکردید حتما امتحان کنید.

Custom Layout

زمانی که چینش خاصی از کنترل‌ها لازم باشه، می‌تونید نحوه چینش کنترل‌ها رو به صورت کاملا سفارشی انجام بدید. کمتر چنین نیازی پیش میاد ولی به هر حال خوب هست که بدونید یک چنین امکانی وجود داره. برای این منظور، میشه از رویداد Layout مربوط به Control و یا override کردن مشخصه LayoutEngine کنترل استفاده کرد.
برای مثال فرض کنید چینشی شبیه این مد نظر هست:

WinForms_CustomLayout
شکل 9 – Layout سفارشی

میشه برای چینش Button ها روی فرم، از رویداد Layout فرم به این صورت استفاده کرد:

void Form1_Layout(object sender, LayoutEventArgs e) {
  // Arrange the buttons in a grid on the form
  Button[] buttons = new Button[] { button1, button2, ..., };
  int cx = ClientRectangle.Width/3;
  int cy = ClientRectangle.Height/3;
  for( int row = 0; row != 3; ++row ) {
    for( int col = 0; col != 3; ++col ) {
      Button button = buttons[col * 3 + row];
      button.SetBounds(cx * row, cy * col, cx, cy);
    }
  }
}

البته، با استفاده از TableLayoutPanel هم میشه بدون نوشتن کد این Layout‌ رو اعمال کرد.

منابع

Windows Forms Layout