مقدمه
هر چند برنامه سازی برای محیط ویندوز به رونق قبل نیست و وب بخشی زیادی از بازار رو تصاحب کرده، ولی همچنان توسعه دهندههای زیادی رو به خودش مشغول کرده و در این حوزه یکی از پس استفاده ترین روش ها همچنان استفاده از Windows Forms از زیر مجوعه .NET Framework هست.در این پست درباره چگونگی Layout و امکانات ارائه شده در WinForms مینویسم.اگه WinForms کار کرده باشید احتمالا با بخشی از این مطالب آشنایی دارین اما ممکنه برخی از مطالب رو اصلا در موردش نشنیده باشید.در این مطلب این موارد نوشته میشه:
- Layout
- Auto Scaling
- Anchoring
- Docking
- Splitting
- Grouping
- Layout Controls
- Custom Layout
Layout
اگه تاالان یک فرم یا واسط گرافیکی طراحی کرده باشین حتما درگیر Layout بودین حتی اگه چیزی در موردش نشنیدید. به طور کلی در حوزه کامپیوتر، Layout به پروسه محاسبه اندازه و محل قرار گیری تعدادی Object یا شی بر اساس تعدادی از محدودیت ها گفته میشه.در WinForms این تعریف رو میشه به محاسبه اندازه و محل قرار گیری کنترل ها روی فرم یا Container دیگه تفسیر کرد.
با آشنایی با امکاناتی که WinForms در اختیار ما گذاشته میتونیم نحوه قرار گیری و اندازه کنترل ها رو بر اساس نیازمون تنظیم کنیم به طوری که واسط طراحی شده در سیستم های مختلف با فونت ها و تنظیمات DPI مختلف بتونه به درستی نمایش داده بشه و یا با تغییر اندازه فرمها محل قرارگیری کنترل ها به صورت مناسب تغییر کنه.در WinForms عمل Layout توسط Layout Engine های پیشفرض تعریف شده برای کنترل ها انجام میشه و البته شما میتونید در صورت نیاز Layout Engine سفارشی ایجاد کنید.
امکانات
احتمالا با مفاهیمی مثل Docking و Anchoring آشنایی دارید. این دو، بخشی از امکانات و نه تمام امکانات ارائه شده هست.این امکانات رو میشه به صورت زیر معرفی کرد:
Auto Scaling
قبل از معرفی این ویژگی، اول در مورد توضیحاتی میدم.کلاس ContainerControl یک زیرکلاس از کلاس ScrollableControl و در ادامه Control هست که ویژگی هایی که مربوط که نگه داری کنترلهای دیگه هست رو داره و کلاس های زیر در WinForms ازین کلاس ارث بری کردن:
- Form
- PropertyGrid
- SplitContainer
- ToolStripContainer
- ToolStripPanel
- UpDownBase
- 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 میبینید که اندازه خودش و کنترل های داخلش تغییر میکنه!
مقدار DPI زمانی کاربر داره که لازمه محل و اندازه کنترلها فقط با تغییر DPI منطبق باشه البته در Windows Vista به بعد تا زمانی که برنامه شما اعلام نکنه که DPI Aware هست استفاده ازین گزینه تاثیری نداره.در این مورد مطالب مختلفی برای گفتن هست که برای طولانی نشدن مطلب ازش میگذرم.مقدار None هم باعث میشه در صورت تغییر Font یا DPI هیچ تغییری صورت نگیره.بهتره از مقدار پیشفرض در این مورد استفاده کنید.
Anchoring
Anchoring برای اتصال یک کنترل به یک یا چند گوشه از کنترل در بر دارندهش (Container) استفاده میشه و زمانی بکار میره که باید اندازه یا محل یک کنترل با تغییر اندازه Container اون، تغییر کنه که این ویژگی با استفاده از مشخصه Anchor انجام میشه.این مشخصه یک یا چند تا از این مقادیر میتونه باشه:
- Left
- Top
- Right
- Bottom
که مقدار پیشفرض Left|Top هست که مشخص میکنه فاصله ضلع سمت چپ کنترل مورد نظر با سمت چپ کنترل در بر دارنده همیشه ثابت هست (صرف نظر از Scaling) و همین موضوع در مورد ضلع بالایی هم صدق میکنه.تصویر زیر، تغییر اندازه فرم با تنظیمات پیشفرض برای کنترل های TextBox و Button رو نشون میده:
همونطور که میبینید، با تغییر اندازه فرم، فاصله ضلع سمت چپ و بالای کنترلها با ضلع سمت چپ و بالای فرم تغییری نکرده چونه مقدار Anchoer پیشفرض اونها Left|Top هست ولی میبینید که فاصله ضلع سمت راست و پایین با تغییر اندازه تغییر کرده.تا اینجا کار خاصی انجام نشده و چیزی هست که به صورت پیشفرض انتظار میره، ولی گاهی لازمه که کنترلها نسبت به این تغییر اندازه واکنش نشون بدن.برای مثال، در تصویر بالا، اگه عرض TextBox با تغییر اندازه فرم تغییر کنه در صورت عریض تر شدن فرم TextBox هم عریض تر میشه و از فضا استفاده بهتری میشه.برای اعمال این رفتار، مشخصه Anchor مربوط به TextBox رو برابر با Left|Right|Top قرار میدیم.این تغییر هم از طریق Designer و هم کد قابل انجام هست.نتیجه به این صورت خواهد بود:
Docking
برخی مواقع لازم هست که کنترل ها از حداکثر فضای موجود در جهت افقی، عمودی یا هر دو استفاده کنند و نمیشه -یا سخت هست که- با استفاده از Anchoring به نتیجه دلخواه رسید و به جای اون میشه از امکان Docking و مشخصه Dock استفاده کرد. مشخصه Dock یکی از این مقادیر میتونه باشه:
- None
- Left
- Top
- Right
- Bottom
- Fill
و مقدار پیشفرض برای کنترل های مختلف متفاوت هست.Docking یک کنترل در یکی از مقادیر Left، Top، Right و Bottom به این معنی هست که اون کنترل، در منتهی علیه همون سمت از کنترل در بر دارنده قرار میگیره و از حداکثر فضا در جهت مخالف استفاده میکنه.برای مثال، مقدار Top باعث میشه کنترل مورد نظر، در بالای کنترل در بر دارنده قرار بگیره و از حداکثر فضای افقی استفاده کنه. شاید رایج ترین مثال، کنترل MenuStrip باشه.
مشخصه Dock این کنترل به صورت پیشفرض برابر Top هست.
البته شاید با استفاده از Anchoring هم بشه یک چنین رفتاری رو ایجاد کرد ولی راه صحیح استفاده از Dock هست. اینکه در هر مورد از چه روشی استفاده بشه با تجربه به راحتی به دست میاد.
مقدار دیگه ای که مشخصه Dock میتونه بگیره Fill هست. این مقدار باعث میشه که کنترل مورد نظر از حداکثر فضای موجود در در دو جهت افقی و عمودی استفاده کنه. برای مثال، طراحی زیر رو در نظر بگیرین:
همونطور که در شکل بالا مشخص هست یک MenuStrip قبلی با Dock پیشفرض و یک ListBox بدون مشخصه Dock ایجاد شدن.ListBox از حداکثر فضای موجود در هیچ کدوم از جهت ها استفاده نکرده. ما میخواهیم از فضای موجود در دو جهت استفاده کنه، فضای فرم رو پر کنه و در صورت تغییر اندازه فرم با اون هماهنگ باشه.بنابراین مشخصه Dock اون برابر Fill قرار دادیم.
نکته
- همه کنترل ها در همه جهت ها قابل رشد نیستن. برای مثال، ارتفاع کنترل TextBox در صورت تک خطی بودن بر اساس Font اون تعیین میشه و با تغییر مشخصه Dock تغییری نمیکنه. در صورت که کنترل سفارشی درست میکنید میتونید با override کردن متد GetPrefrredSize اندازه دلخواه رو برای کنترل تعیین کنید.
- در صورت تداخل بین کنترلهای فرمی که Dock شدن و یا در صورتی که در یک کنترل Container مثل فرم، به بیش از یک کنترل، یک مشخصه Dock بدین، مثلا به دو کنترل مشخصه مقدار Bottom بدین، با استفاده از متدهای BringToFront و SendToBack (از طریق کد یا راست کلیک روی کنترل در محیط Designer) میتونید ترتیب قرار گیری کنترل ها در پایین Container رو تغیر بدید.
Splitting
زمانی که از Docking استفاده میشه، میتونید با استفاده از Splitting برای کاربر امکان تغییر اندازه کنترلهای Dock شده رو فراهم کنید.طراحی زیر رو در نظر بگیرید:
در این طرح، به طرح قبلی یک ListBox با مشخصه Dock با مقدار Right اضافه کردیم تا در سمت راست فرم قرار بگیره. همونطور که میبینید، با تغییر اندازه فرم، ListBox سمت چپ با مشخصه Dock با مقدار Fill، فضای ایجاد شده رو در اختیار میگیره ولی ListBox سمت راست تغییر اندازه نمیده. در صورتی که بخوایم به کاربر امکان تغییر عرض ListBox راستی رو بدیم، میتونم از کنترل Splitter استفاده کنیم. برای این کار یک کنترل Splitter با مقدار Right برای Dock به فرم اضافه میکنیم.
همونطور که میبینید کنترل مورد نظر بین دو کنترل ListBox قرار گرفته. ممکنه لازم بشه تا با استفاده از تغییر Index کنترلها (با راست کلیک روی اونها و استفاده از Bring to Front یا Send to Back) کنترلها در جای مناسب قرار بدید.کاربر میتونه با آوردن اشاره گر موس روی کنترل Splitter فضای ختصاص داده شده به کنترلها رو تغییر بده.
Grouping
گاهی لازمه برای رسیدن به Layout مورد نظر، کنترلهای فرم رو دسته بندی کنید. برای این کار میتونید از کنترلهایی مثل GroupBox، Panel و یا TabControl استفاده کنید. این کنترلها، میتونن تعدادی کنترل رو در خودشون نگه دارن. (همونطور که فرم کنترلهای دیگه رو نگه میداره.) برای مثال، ما لازم داریم که بالای لیستی که در طرح قبل همه فضای فرم رو با Fill شدن گرفته بود، تعدادی TextBox و یک Buttonاضافه کنیم. از اونجایی که ListBox رو فرم Dock هست و در حالت عادی از همه فضا استفاده میکنه، نمیتونیم TextBox ها و Button رو مستقیما به فرم اضافه کنیم چون ListBox روی اونها قرار میگیره. برای این کار، از یک Panel با مقدار Dock برابر Top استفاده میکنیم و کنترلها رو روی اون میگذاریم.
Layout Controls
دو کنترل پر کاربر ارائه شده در WinForms برای ایجاد Layout خاص، کنترلهای FlowLayoutPanel و TableLayoutPanel هستن. این دو کنترل از LayoutEngine های متفاوتی در مقایسه با سایر کنترلهای ارائه شده استفاده میکنن.
با استفاده از FlowLayoutPanel میتونید تعدادی کنترل رو به صورت جاری در کنار همدیگه قرار بدید. محل قرار گیری کنترلها بر اساس اندازه کنترل در بر دارنده (FlowLayoutPanel) و اندازه سایر کنترلها تغییر میکنه.
با استفاده از TableLayoutPanel میتونید کنترلها رو صورت جدول مانند کنار همدیگه قرار بدین به طوری که موقع تغییر اندازه فرم به صورت مناسب تغییر اندازه بدن. با استفاده از این کنترل کمتر درگیر نظم کنترلها و تراز کردن اونها میشید. در مورد این کنترلها توضیح زیادی نمیدم اما اگه از اونها در طراحی استفاده نکردید حتما امتحان کنید.
Custom Layout
زمانی که چینش خاصی از کنترلها لازم باشه، میتونید نحوه چینش کنترلها رو به صورت کاملا سفارشی انجام بدید. کمتر چنین نیازی پیش میاد ولی به هر حال خوب هست که بدونید یک چنین امکانی وجود داره. برای این منظور، میشه از رویداد Layout مربوط به Control و یا override کردن مشخصه LayoutEngine کنترل استفاده کرد.
برای مثال فرض کنید چینشی شبیه این مد نظر هست:
میشه برای چینش 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 رو اعمال کرد.