لماذا كتبت عن هذا الموضوع؟
ومن وجهة نظري الشخصية ، ان موضوع التخطيط (Layout) في WPF هو الصعب السهل ، فهو صعب لإن الطريقة المتبعة في WPF مختلفة بعض الشئ عن ما اعتدنا عليه في تقنيات سابقة مثل Windows Form وغيرها، وسهل لأن العملية بمجملها تعني اختيار احد ألواح التخطيط ثم وضع العناصر بداخلها(كما سنرى). وقد يكون هذا احد الأسباب التي دفعتني لأن اكتب حول هذا الموضوع.
فيما مضى:
تعتبر عملية التخطيط (Layout) من اهم العمليات في تصميم واجة المستخدم حيث اننا نقضي معظم الوقت في تحديد حجم وموضع الكنترولات في النافذة لكي يكون الشكل النهائي جذاب ، عملي ومرن في نفس الوقت. لكن التحدي الحقيقي والصعوبة الحقيقية تكمن في التأكد من ان هذا التخطيط يمكن ان يتكيف تلقائيا عندما يتغير حجم النافذة او تتغير دقة الشاشة.
في الإصدار الأول Windows Form كانت عملية التخطيط تتم عن طريق تحديد موقع وحجم كل كنترول ، بالإضافة إلى امكانية استخدام خصائص الإرساء (anchoring) والرصف (docking) التي ساعدتنا كثيرا خلال هذه العملية. ورغم جمال هاتين الخاصيتين ومساعدتها لنا في إنشاء نوافذ و مربعات حوار بسيطة ، إلا انها لم تكن مفيدة في حالات اخرى كثيرة. مثل تقسيم مساحة من النافذة إلى منطقتين متساويتين ومتجاورتين في نفس الوقت (
bi-pane)، كذلك فإنها لم تكن مفيدة في الحالات التي يمكن ان يتغير فيها المحتوى بشكل ديناميكي، على سبيل المثال وضع مربع تسمية (Label) بجوار مربع النص (TextBox) ، قد يؤدي إلى التداخل بينهما إذا تغير مربع التسمية بحيث صار محتويا على نص اطول مما كان متوقعاً.
ولسد هذه الفجوة جاء الإصدار الثاني من Windows Form ليقدم الواح مثل FlowLayoutPanel و TableLayoutPanel ، يمكن من خلالها إنشاء نوافذ متطورة ومشابهة لحد كبير لصفحات الوب.
مثل هذه الألواح تسمح للكنترولات بإن تنمو وتكبر وان تزيح الكنترولات الاخرى المجاورة لها بشكل تلقائي. مما سهل علينا التعامل مع المحتوى الديناميكي ، وإنشاء الواجهات المحلية .
لكن المشكلة ظلت في ان عملية التخطيط (Layout) تعتمد اساسا على أحداثيات ثابتة يصعب التعامل معها من اجل إنشاء واجهات اكثر مرونة.
جاءت WPF لتحل هذه المشكلة ، عن طريق ابتكار نظام تخطيط جديد تأثر كثيرا بالاسلوب السابق في Windows Form ، وقام بتغيير الأسلوب القديم الذي يقوم على اساس الأحداثيات الثابتة ، ليصبح التخطيط قائما على اساس التتبع(flow-based layout) .
والنتيجة انه اصبح بامكاننا إنشاء واجهات اكثر تطورا واكثر تعقيدا من السابق، لها ان تعمل بشكل ممتاز مهما كانت دقة الشاشة المستخدمة ، ومهما كان التغيير الحاصل في حجم النافذة .
فلسفة التخطيط في WPF:
لو نظرنا إلى النوافذ في WPF سوف نجد ان كل نافذة يمكن ان تحتوي على عنصر واحد فقط (كون النوافذ مشتقة من الفئة ContentControl). ومن اجل ان نقوم بإضافة اكثر من عنصر اليها فلا بد اولا من ان نضع فيها احد الألواح ثم نضيف اليه العناصر المطلوبة.
التخطيط في WPF بشكل عام يعتمد على هذه الفكرة ، وهي اختيار اللوح المناسب ثم اضافة العناصر اليه.
ومن الجيد هنا ان نذكر بعض القواعد الهامة التي يفضل اتباعها عند تصميم الواجهة:
- يجب ان لا نحدد حجم العناصر بشكل صريح، وبدلا من هذا فإن كل عنصر يمكنه ان يحدد الحجم المناسب له استنادا على ما يحتويه ، فالزر مثلا يمكن ان يتسع تلقائيا كلما زدنا من طول النص بداخله.
- موقع اي عنصر لا يتم تحديده على اساس الاحداثيات (س،ص)، واللوح هو من يقوم بترتيب العناصر وتحديد موضعها في النافذة استنادا على: 1) حجم كل عنصر، 2) ترتيبه داخل اللوح ،3) بعض المعلومات الأخرى التي يتم تزويد اللوح بها من خلال الخصائص المرفقة (Attached Properties).
- تقوم الالواح بتقسيم المساحة المتاحة لها على عناصرها الأبناء، حيث تحاول ان تعطي كل عنصر الحجم الذي يناسبه(استنادا على ما يحتويه العنصر).
- الألواح يمكن ان تتداخل فيما بينها، حيث ان اي لوح يمكن ان يحتوي بداخله على لوح آخر ويقوم اللوح الأب بالتعامل مع اللوح الأبن بنفس الطريقة التي يتعامل بها مع اي عنصر ابن آخر.
عملية التخطيط في WPF
تمر عملية التخطيط (Layout) في WPF بمرحلتين هما: مرحلة القياس (measure) ومرحلة الترتيب (arrange) ، في مرحلة القياس يقوم اللوح بالمرور على العناصر بداخله ويسئل كل عنصر عن الحجم الذي يناسبه. وفي مرحلة الترتيب يقوم اللوح بوضع كل عنصر في مكانه المناسب.
خلال هذه العملية ليس من الضروري ان يتم اعطاء العنصر الحجم الذي يريده تماما ، فقد لا يكون اللوح كافياً لمثل هذا العنصر وفي هذه الحالة يحدث اقتصاص لبعض أجزاء العنصر.
ملاحظة هامة:
تذكر ان الالواح لا تدعم عملية التمرير (scrolling). وان هناك عنصر خاص بهذه العملية هو ScrollViewer يمكن استخدامه في اي مكان وداخل اي عنصر آخر.
الواح التخطيط
جميع الألواح في WPF مشتقة من الفئة المجردة Panel (تقع في المجال System.Windows.Controls)،
ولعل الخاصية Children هي اهم خاصية فيها حيث تمثل كولكشن يحتوي على العناصر الأبناء داخل اللوح.
اهم الفئات المشتقة من Panel هي:
- StackPanel: تقوم بتكديس العناصر بشكل عمودي او افقي.
- WrapPanel: تقوم بترتيب العناصر على شكل اسطر، حيث يتم تعبئة السطر الأول ثم الثاني وهكذا.
- DockPanel: تقوم برصف العناصر في جانب من النافذة ، وتجعل العنصر الأخير يشغل المساحة المتبقية.
- Grid: تقوم بترتيب العناصر على شكل صفوف واعمدة.
- UniformGrid: تقوم بترتيب العناصر على صفوف واعمدة ، لكن جميع الخلايا فيها يكون لها نفس الحجم.
- Canvas: تسمح بوضع العناصر على اساس الأحدثيات (س،ص) . وهذا اللوح يشبه إلى حد كبير الطريقة المستخدمة في WinForm لكن لا يوجد دعم لمميزات anchoring و docking
إضافة إلى ان هناك الواح مخصصة لبعض الكنترولات مثل اللوح TabPanel الذي يستخدم في ترتيب التبويبات داخل TabControl، واللوح ToolbarPanel الذي يستخدم في ترتيب الأزرار في شريط الأدوات (Toolbar) ، وغيرها من الألواح.
استخدام StackPanel
يعتبراللوح StackPanel من ابسط الألواح على الأطلاق ، حيث يقوم بترتيب وتكديس العناصر بشكل عمودي او افقي . ويتم تحديد اتجاه التكديس عن طريق الخاصية Orientation التي يمكن ان تكون قيمتها:
- Vertical *( القيمة الأفتراضية): من اجل ترتيب العناصر بشكل عمودي من الأعلى إلى الأسفل .
- Horizontal: من اجل ترتيب العناصر بشكل افقي من اليسار إلى اليمين.
مثال1:
كود :
<Window x:Class="SimpleLayout.Window1"
xmlns="[url=http://schemas.microsoft.com/winfx/2006/xaml/presentation]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url]"
xmlns:x="[url=http://schemas.microsoft.com/winfx/2006/xaml]http://schemas.microsoft.com/winfx/2006/xaml[/url]"
Title="مثال بسيط على Layout" Height="300" Width="300">
<StackPanel>
<Label>A Button Stack</Label>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
<Button>Button 4</Button>
</StackPanel>
</Window>
النتيجة:
[attachment=91210:image002.jpg]
مثال2:
نفس المثال السابق مع تغيير اتجاه التكديس.
كود :
<Window x:Class="SimpleLayout.Window1"
xmlns="[url=http://schemas.microsoft.com/winfx/2006/xaml/presentation]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url]"
xmlns:x="[url=http://schemas.microsoft.com/winfx/2006/xaml]http://schemas.microsoft.com/winfx/2006/xaml[/url]"
Title="مثال بسيط على Layout" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<Label>A Button Stack</Label>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
<Button>Button 4</Button>
</StackPanel>
</Window>
النتيجة:
[attachment=91212:image004.jpg]
خصائص العناصر المتعلقة بالتخطيط :
رغم ان التخطيط هو من مسئولية اللوح بشكل اساسي إلا ان لكل عنصر بعض الخصائص التي يمكن ان تؤثر خلال هذه العملية. هذه الخصائص هي:
- HorizontalAlignment: تحدد الكيفية التي يتم بها وضع العنصر الأبن داخل اللوح عندما تكون له مساحة افقية فائضة . ويمكن ان تأخذ احدى القيم التالية : Left ، Center ، Right او Stretch *.
- VerticalAlignment: تحدد الكيفية التي يتم بها وضع العنصر الأبن داخل اللوح عندما تكون له مساحة رأسية فائضة. ويمكن ان تأخذ احدى القيم التالية: Top ، Center ، Bottom او Stretch *.
- Margin: تحدد حجم الهامش الذي يجب ان يترك في كل جانب من جوانب العنصر . نوع هذه الخاصية هو Thickness وهو عبارة عن سجل يمكن ان يحتوي على رقم محدد لكل جانب من الجوانب الأربعة (يسار، اعلى، يمين ،اسفل)، القيمة الأفتراضية لهذه الخاصية هو (0, 0، 0، 0) ما يعني ان العنصر ليس له اي هامش(تذكر دائما ان القيمة الأفتراضية لإي خاصية قد تختلف في بعض العناصر).
- MinWidth و MinHeight: لتحديد الحد الأدنى لعرض و ارتفاع العنصر.
- MaxWidth و MaxHeight: لتحديد الحد الأعلى لعرض و ارتفاع العنصر.
- Width و Height: لتحديد عرض وارتفاع العنصر بشكل صريح، بمعنى ان العنصر سوف يظهر بذلك الحجم بغض النظر عن محتواه (هل يكفي ام لا؟ ) وبغض النظر عن خصائص المحاذاة الأفقية او الرئيسية.(لهذا ينصح بعدم استخدام خاصيتي العرض او الأرتفاع او التقليل من استخدامها)
جميع هذه الخصائص معرفة في الفئة FrameworkElement والتي تتفرع منها معظم العناصر في واجهة المستخدم ، بما فيها الألواح نفسها.
ولكي نفهم هذه الخصائص فلا نبد ان نتناولها بشئ من التفصيل.
المحاذاة (Alignment):
تحدد خصائص المحاذاة في العنصر اين وكيف يتم وضع العنصر داخل المساحة المعطاه له من قبل اللوح الأب.
بالنسبة للمحاذاة الأفقية (HorizontalAlignment) يمكن ان تأخذ قيمة من التالي:
- Stretch *: وتعني ان العنصر سوف يتم تمديده ليشغل كل المساحة الممنوحة له.
- Left: وضع العنصر في الجانب الأيسر.
- Center: توسيط العنصر
- Right: وضع العنصر في الجانب الأيمن
اما المحاذا الرئيسية (VerticalAlignment) فيمكن ان تكون:
- Stretch *: وتعني ان العنصر سوف يتم تمديده ليشغل كل المساحة الممنوحة له.
- Top: وضع العنصر في الأعلى
- Center: توسيط العنصر
- Bottom: وضع العنصر في الأسفل
الكود التالي يوضح كيفية التعامل مع المحاذاة الأفقية .
مثال3:
كود :
<Window x:Class="ATS.WPFLayout2.Phase1.L_StackPanel.L_StackPanel3"
xmlns="[url=http://schemas.microsoft.com/winfx/2006/xaml/presentation]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url]"
xmlns:x="[url=http://schemas.microsoft.com/winfx/2006/xaml]http://schemas.microsoft.com/winfx/2006/xaml[/url]"
Title="خصائص المحاذاة" Height="300" Width="300">
<StackPanel>
<Label HorizontalAlignment="Center">A Button Stack</Label>
<Button HorizontalAlignment="Left">Button 1</Button>
<Button HorizontalAlignment="Right">Button 2</Button>
<Button HorizontalAlignment="Stretch">Button 3</Button>
<Button>Button 4</Button>
</StackPanel>
</Window>
النتيجة:
[attachment=91211:image006.jpg]