الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

دورة XAML

مثبّت
بدأه زكيري عبد العالي في 18 يونيو 2011 · 0 رد · 4,049 مشاهدة · في SilverLight
مشاركة: واتساب X فيسبوك تيليجرام
#1

ملاحضة الكود بالدلفي و السي شارب

السلام عليكم

لقد فصلت xml من ندرس wpf لاني ارى انه يستحق اهتمام كبير وباذن الله سنقوم بشرح الامور من 0 ان شاء الله .

تعلم XAML

- اساسيات XAML

- مجالات الأسماء في XAML

- Code-Behind

- تسمية العناصر:

- التعامل مع الخصائص البسيطة:

- محولات الأنواع:

- التعامل مع الخصائص المركبة

- خصائص الأحتواء:

- لخصائص المرفقة (Attached Properties)

- تمديدات الوسوم

- ستخدام فئات من مجالات اسماء اخرى:

- استخدام فئات من مجالات اسماء اخرى

التخطيط في WPF

- فلسفة التخطيط في WPF:

- عملية التخطيط في WPF

ربط البيانات DataBinding في WPF

* مقدمّة

* نظام التبعيّة Dependency System في WPF, ومفاهيم ربط البيانات الأساسية

* ربط البيانات من خلال XAML

* ربط البيانات إجرائيًا

* أوضاع الربط والتحديث Data Binding/Updating Modes

* خيارات الربط Data Binding Options

عبد الكريم كنعان

اقتباس

مقدمة: إن XAML هي اختصار Extensible Application Markup Language حيث تلفظ (XAML) بـ Zamel، وهي تستخدم من أجل بناء واجهات لتطبيقات .NET في الويندوز Vista ولكن يمكن استخدامها مه ويندوز XP وويندوز Server 2003، أتت XAML من أجل الفصل بين واجهات المستخدم والكود مثل ASP.NET حيث يمكنك وضع (كودات) ASP.NET في ملف والكود التنفيذي (مثلا C# أو VB) في ملف آخر، عندما يتم ترجمة ملفات XAML فإنها تترجم إلى BAML حيث أنها مشاهة إلى XAML ولكنها أسهل للقراءة وبالتالي هي أسرع في التنفيذ.

دعنا الان عزيزي المبرمج نكتب سويا أول برنامج في XAML.

تعلم Xaml

الكاتب HassanAlattas

[b

اقتباس
]مقدمة:

XAML (اختصار لـ Extensible Application Markup Language وتلفظ "زامل")، وهي لغة وسوم لتمثيل الكائنات في الدوت نت، تم إنشائها خصيصاً من اجل WPF وتستخدم في تقنيات اخرى مثل WF (Workflow Foundation) و Silverlight.

في WPF تستخدم XAML اساسا من اجل تعريف واجهات المستخدم والغاية من هذا هو فصل الأجزاء المتعلقة بتعريف الواجهة عن الكود في التطبيق. بحيث يكون بامكان المصممين إنشاء الواجهات باستخدام ادوات مخصصة للتعامل مع الصور والرسومات مثل Microsoft Expression Blend، بينما يقوم المطورين بتعريف منطق وسلوك تلك الواجهات من خلال الكود باستخدام ادوات مثل Visual Studio.

تعتمد لغة XAML اساسا على لغة XML ، لذا فإن اي مستند XAML في الواقع هو ملف XML ، ويخضع لنفس القواعد والشروط، مثلها مثل اي لغة وسوم اخرى ، إلا ان XAML اكثر صرامة من غيرها حيث ان كل عنصر فيها يتم تحويله إلى كائن في الدوت نت.

وكونها تعتمد على XML ، فإن هذا اضاف سهولة ومرونة كبيرة ، فيمكن لاكثر من اداة ان تتعامل مع نفس المستند في نفس الوقت ، ومن السهل جدا قراءة مستندات XML ونقلها او حملها من جهاز إلى آخر.

وقد يظن البعض ان اعتماد XAML على XML له اثر على الأداء ، فمن المعروف ان XML لديها بعض المشاكل المتعلقة بهذا الشأن نظرا لأن المستند يمكن ان يحتوي على الكثير من الوسوم ما يجعل عملية الأعراب تأخذ وقتا اطول.

وللتغلب على هذه المشكلة فإن WPF تقوم بتحويل مستند XAML إلى الصورة الثنائية المكافئة له والتي تسمى بـ BAML (اختصار لـ Binary Application Markup Language)، وهذه التحويل يحدث بدون ان يشعر المبرمج خلال عملية الترجمة.

اساسيات XAML:

حتى نفهم XAML ، علينا ان نتذكر القواعد التالية:

- كل عنصر في مستند XAML يتم ترجمته إلى مثيل من فئة في الدوت نت. ودائما يكون اسم العنصر مطابقا لأسم فئة معينة، مثلا العنصر <Button> يمثل امر لـ XAML من اجل إنشاء كائن من الفئة Button.

- كما هو الحال في اي مستند XML ، يمكن للعناصر ان تتداخل، وتقوم XAML بتفسير هذا التداخل على انه احتواء ، فإذا وجدت عنصر Button داخل عنصر Grid ، فإن هذا يعني ان الواجهة سوف تتضمن Grid يحتوي على زر

- يمكن تعيين الخصائص من خلال الواصفات (Attributes) ، لكن في بعض الحالات تكون الواصفة غير كافية لتحديد قيمة الخاصية ، لذا فإننا نستخدم صيغة اخرى تسمى بالعنصر الخاصية (Property Element).

لكن قبل ان ندخل في التفاصيل ، لنحاول اولاً ان نلقي نظرة على مستند XAML يمثل نافذة جديدة في الفيجوال استديو(الأسطر مرقمة للتوضيح فقط):

1 <Window x:Class="WpfApplication1.Window1"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 Title="Window1" Height="300" Width="300">
5   <Grid>

6   </Grid>
7 </Window>

كما تلاحظ إن هذا المستند يحتوي على عنصرين فقط ، هما العنصر الجذر والمتمثل في النافذة والعنصر الآخر هو Grid يمكن وضع عناصر اخرى بداخله مثل الكنترولات والأشكال والصور وغيرها.

وبشكل عام يمكن ان يكون العنصر الجذر من اي نوع ، لكنه في WPF غالبا ما يكون احد الأنواع التالية:

- Window

- Page

- Application

- ResourceDictionary

وكما هو الحال مع اي مستند XML فإن كل مستند XAML يجب ان يحتوي على عنصر جذر واحد فقط ، ما يعني انه بمجرد ان نغلق عنصرالنافذة بالوسم </Window> فإن المستند ينتهي عند هذا الحد ولا يمكن ان يأتي اي شئ بعده.

إذا نظرنا إلى الوسم الأفتتاحي للنافذة فإننا سوف نجد العديد من الواصفات التي تم استخدامها، فهناك واصفة الفئة المقابلة ، وواصفتين من اجل تحديد مجالين للاسماء ، بينما الواصفات:

4 Title="Window1" Height="300" Width="300">

يمثل كل منها خاصية معينة في النافذة وبالتالي فإن النافذة يكون عنوانها Window1 وحجمها 300 في 300 وحدة.

مجالات الأسماء في XAML

من المعروف ان الفئات في الدوت نت تكون داخل مجالات الأسماء(Namespaces) ، وحتى تستطيع XAML تحديد الفئة المقابلة فإنها تحتاج إلى اسم الفئة واسم المجال الذي يحتويها.

وكما عرفنا إن اسم الفئة يتم تحديده في اسم العنصر ، بينما اسم المجال يتم تحديده من خلال الواصفة xmlns الخاصة بـ XML . وعندما ننظر في مستند XAML السابق فإننا نجد ان هناك مجالين اسماء هما:

2  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

هذين المجالين سوف نجدهما في معظم مستندات XAML وكل مجال يؤدي وظيفة معينة:

- المجال http://schemas.microsoft.com/winfx/2006/xaml/presentation وهو خاص بـ WPF و يتضمن جميع الفئات في WPF بما فيها الكنترولات التي سوف نستخدمها من اجل إنشاء الواجهة. هذا المجال تم التصريح عنه بدون اي بادئة لذا يتم التعامل معه على انه المجال الأفتراضي في المستند، واي عنصر سوف ينتمي لهذا المجال بشكل افتراضي.

- المجال http://schemas.microsoft.com/winfx/2006/xaml وهو خاص بـ XAML ويتضمن بعض الوظائف والعناصر المساعدة في إنشاء المستند. نلاحظ ان هذا المجال قد تم التصريح عنه مع البادئة x ، وهذا يعني انه من اجل استخدام اي عنصر ينتمي له فيجب ان نكتب اسمه مسبوقا بتلك البادئة ، مثل : <x:ElementName>.

هذا النوع من التصريح عن مجال الأسماء يسمى بالمجال القائم على اساس URI ، وهناك نوع آخر سوف نتطرق له لاحقا، لكن ما يميز هذه الطريقة ان المجال في XML يتضمن اكثر من مجال مقابل في الدوت نت ، مثلا http://schemas.microsoft.com/winfx/2006/xaml/presentation يتضمن الكثير من المجالات في الدوت نت (جميعها تبدأ بـ System.Windows).

Code-Behind

نستطيع من خلال XAML إنشاء وتعريف واجهة المستخدم ، لكن من اجل تعريف الوظائف في التطبيق التي ستستجيب لأحداث المستخدم فإننا نحتاج إلى كتابة الكود .

ربط مستند XAML بفئة في كود التطبيق يتم من خلال الواصفة x:Class التي تكون صيغتها بهذا الشكل:

<object x:Class="namespace.classname"...>
  ...
</object>

في المثال السابق سوف نجد:

1 <Window x:Class="WpfApplication1.Window1"

وهي تخبر XAML بإن يقوم بإنشاء فئة جديدة اسمها Windows1 في المجال WpfApplication1 وتكون مشتقة من الفئة Window .

لاحظ ان هذه الواصفة تبدأ بـ x ما يعني انها تنتمي إلى المجال الخاص بـ XAML ويمكن استخدامها مع العنصر الجذر فقط.

يتم تجزئة الفئة Windows1 في ملفين هما:

- الملف الأول(Window1.g.cs) : يحتوي على الكود الذي يتم توليده تلقائيا في زمن الترجمة (compile time).

- الملف الثاني (Window1.xaml.cs): من اجل ان نقوم بتعريف الدوال التي تعالج الأحداث التي تصدر من العناصر في النافذة، وهذا الملف هو الذي سنتعامل معه بشكل اساسي.

عندما نقوم بالقاء نظرة على الملف Window1.xaml.cs سوف نجده بهذا الشكل:

namespace WpfApplication1 ;

interface

type
  Window = public partial class(System.Windows.Window)
  public constructor;
end;

implementation

constructor Window;
begin
  InitializeComponent();
end;

end.

وكما نرى ان هذه الفئة حتى الآن لا تحتوي على اي دالة باستثناء المشيد الأفتراضي الذي يقوم باستدعاء الدالة InitializeComponent() ، وهذه الدالة يتم توليدها تلقائيا خلال عملية الترجمة ، ولا تحتاج منا إلى اي تعديل (في الواقع اي تعديل عليها بشكل يدوي سوف يختفي خلال عملية التوليد التالية).

وإذا احتجنا إلى ان نضيف كود إلى المشيد ، فيجب ان نتأكد انه يقوم باستدعاء InitializeComponent().

تسمية العناصر:

غالبا ما نحتاج إلى كتابة الكود (Code-Behind) الذي يقوم بتغيير خصائص عنصر او اكثر في واجهة المستخدم ، ويمكننا الوصول إلى اي عنصر بشرط ان نقوم بتسميته.

تسمية العناصر في XAML تتم من خلال الواصفة x:Name التي يمكن استخدامها مع اي عنصر يمثل كائن، فلو اردنا ان نسمي العنصر Grid:

<Grid x:Name="grid1">

ويقوم XAML تلقائيا بإضافة حقل إلى الفئة يمثل ذلك العنصر:

internal System.Windows.Controls.Grid grid1;

ومن خلال هذا الحقل يمكننا التعامل مع العنصر Grid

MessageBox.Show(grid1.ActualHeight.ToString());

تذكر ان الواصفة x:Name تنتمي إلى لغة XAML ، ويمكن تطبيقها على اي عنصر في XAML .

استخدام الخاصية Name في العناصر:

الكثير من العناصر في WPF تحتوي على الخاصية Name . ومثل هذا العناصر يمكن تسميتها سواء باستخدام الواصفة x:Name او باستخدام الخاصية Name .

فالعنصر Grid على سبيل المثال يحتوي على الخاصية Name (المعرفة اساسا في الفئة الأب FrameworkElement) ويمكن تسميته من خلالها بالشكل التالي:

<Grid Name="grid1">

وسواء استخدمت الواصفة x:Name او الخاصية Name فإن النتيجة تكون متساوية ، حيث يتم تحديد اسم الحقل وتحديد قيمة الخاصية Name .

التعامل مع الخصائص البسيطة:

اصبح معروفا لدينا ان الواصفات في العنصر تقوم بتعيين الخصائص في الكائن المقابل في الدوت نت.

فلو اردنا إنشاء زر يحتوي على النص "This is a button" ويكون لون الخط احمر وخلفيته زرقاء ، فإننا نكتب:

<Button Background="Blue" Foreground="Red" Content="This is a button"/>

ويمكن استخدام اي واصفة في XAML ، بشرط ان يكون لها خاصية مقابلة تنتمي إلى فئة الكائن في الدوت نت، وسوف تعترض XAML على اي واصفة لا تكون كذلك.

يمكننا ايضاً عمل نفس الزر السابق من خلال كود C# بالشكل التالي:

 var btn: Button := new Button();
        btn.Background := Brushes.Yellow;
        btn.Foreground := Brushes.Red;
        btn.Content := 'This is a button';

محولات الأنواع:

تكون القيمة الواصفة دائما عبارة عن نص ، و المشكلة ان نوع الخصائص المقابلة قد يكون من اي نوع ، ففي الزر السابق ، سوف نجد ان الخاصية Background من النوع Brush .

وحتى تستطيع XAML تعيين مثل هذه الخصائص فلا بد لها اولا ان تقوم بتحويل القيمة النصية إلى قيمة مكافئة من نوع الخاصية المقابلة.

عملية التحويل تتم من خلال محولات النوع (TypeConverters) التي تقوم اساسا بتحويل قيمة من نوع إلى آخر. مثلا المحول BrushConverter مختص بتحويل القيمة من وإلى كائن من النوع Brush.

إذا لم يستطع المحول تحويل القيمة ، او إذا لم تستطع XAML تحديد المحول المرافق للخاصية (او نوعها) فإنها تقوم بتوليد خطأ وتتوقف العملية.

لاحظ ايضاً ان XAML متحسسة لحالة الأحرف فيما يتعلق باسماء العناصر والواصفات، بينما لا تكون متحسسة لحالة الأحرف مع القيم ، لإن محولات الأنواع يمكنها تحويل القيمة النصية بغض النظر عن حالة الأحرف فيها.

التعامل مع الخصائص المركبة

هناك بعض الخصائص التي لا يمكن تعيينها من خلال الواصفات ، بسبب ان قيمتها قد تمثل كائن او عدة كائنات يصعب التعبير عنها بنص بسيط.

لهذا وفرت XAML صيغة اخرى لتعيين الخصائص في الكائنات تسمى بـ "صيغة العنصر الخاصية" (Property Element Syntax) ، وفيها نقوم بإضافة عنصر XML يمثل الخاصية ويكون اسمه على شكل <TypeName.Property> وينتهي بـ </TypeName.Property> ، ويتم تحديد القيمة او القيم المطلوبة داخل هذا العنصر.

مثلا لو اردنا إنشاء نفس الزر السابق باستخدام العنصر الخاصية فإننا نكتب:

<Button>
  <Button.Background>
	<SolidColorBrush Color="Blue"/>
  </Button.Background>
  <Button.Foreground>
	<SolidColorBrush Color="Red"/>
  </Button.Foreground>
  <Button.Content>
	This is a button
  </Button.Content>
</Button>

لاحظ انه من خلال "العنصر الخاصية" نستطيع ان نحدد او نخصص نوع الكائن الذي يمثل القيمة واي خصائص اخرى بداخله.

فلو اردنا ان نجعل الزر محتويا على صورة بدل النص فإننا سوف نقوم بإضافة عنصر يمثل الصورة بداخل الخاصية:

<Button>
	<Button.Content>
		<Image Source="help.png"/>
	</Button.Content>
</Button>

و لو اردنا استخدام Brush من نوع LinearGradientBrush لكي نجعل لون الخلفية بلون متدرج فإننا سوف نستخدم :

<Button>
  <Button.Background>
	<LinearGradientBrush>
	</LinearGradientBrush>
  </Button.Background>
</Button>

ومن اجل ان يعمل هذا التدرج بشكل سليم، فلا بد من تحديد الألوان في LinearGradientBrush ، من خلال تعبئة الخاصية GradientStops والتي تمثل كولكشن كائنات من النوع GradientStop، ومثل هذه الخاصية يمكن تعيينها في XAML من خلال "العنصر الخاصية" فقط:

<Button>
	<Button.Background>
		<LinearGradientBrush>
			<LinearGradientBrush.GradientStops>
			</LinearGradientBrush.GradientStops>
		</LinearGradientBrush>
	</Button.Background>
</Button>

يمكننا بعد ذلك إضافة الكائنات GradientStop وتحديد الألوان المطلوبة :

<Button>
	<Button.Background>
		<LinearGradientBrush>
			<LinearGradientBrush.GradientStops>
				<GradientStop Offset="0.00" Color="Red" />
				<GradientStop Offset="0.50" Color="Indigo" />
				<GradientStop Offset="1.00" Color="Violet" />
			</LinearGradientBrush.GradientStops>
		</LinearGradientBrush>
	</Button.Background>
</Button>

يكون الكود المكافئ بهذا الشكل:

  var brush: LinearGradientBrush := new LinearGradientBrush();
   var gradientStop1: GradientStop := new GradientStop();
       gradientStop1.Offset := 0;
       gradientStop1.Color := Colors.Red;

     brush.GradientStops.Add(gradientStop1);
  var gradientStop2: GradientStop := new GradientStop();
      gradientStop2.Offset := 0.5;
      gradientStop2.Color := Colors.Indigo;

     brush.GradientStops.Add(gradientStop2);
  var gradientStop3: GradientStop := new GradientStop();
      gradientStop3.Offset := 1;
      gradientStop3.Color := Colors.Violet;
     brush.GradientStops.Add(gradientStop3);
     button.Background := brush

يمكننا ايضاً مع بعض الخصائص (التي يكون لها set) تحديد نوع الكولكشن المستخدم :

<LinearGradientBrush>
	<LinearGradientBrush.GradientStops>
		<GradientStopCollection>
			<GradientStop Offset="0.00" Color="Red" />
			<GradientStop Offset="0.50" Color="Indigo" />
			<GradientStop Offset="1.00" Color="Violet" />
		</GradientStopCollection>
	</LinearGradientBrush.GradientStops>
</LinearGradientBrush>

خصائص الأحتواء:

تسمح XAML لأي فئة بإن تحدد احدى الخصائص فيها كخاصية احتواء افتراضية، واي عنصر من تلك الفئة يمكن ان يحتوي على عنصر او عدة عناصر بداخله مباشرة بدون الحاجة إلى تحديد عنصر الخاصية.

مثلا خاصية الأحتواء الأفتراضية في الزر هي الخاصية Content لذا يمكننا عمل نفس الزر السابق بدون تحديد هذه الخاصية :

<Button>
	<Image Source="help.png"/>
</Button>

ايضا خاصية الأحتواء الأفتراضية في LinearGradientBrush هي GradientStops فيمكننا إنشاء التدرج السابق بالشكل التالي:

<LinearGradientBrush>
	<GradientStop Offset="0.00" Color="Red" />
	<GradientStop Offset="0.50" Color="Indigo" />
	<GradientStop Offset="1.00" Color="Violet" />
</LinearGradientBrush>

تعددت الطرق والنتيجة واحدة:

مما سبق ، يمكن تعيين اي خاصية باكثر من طريقة ، مثلا من اجل تعيين الخاصية Content في الزر فإننا نستخدم اي طريقة من التالي:

<Button Content="Click Me"/>

<Button>
	<Button.Content>
		Click Me
	</Button.Content>
</Button>

<Button>
	Click Me
</Button>

الخصائص المرفقة (Attached Properties)

بإلاضافة إلى الخصائص الاعتيادية والتي نتعامل معها دائما ، قامت XAML بإضافة مبدأ جديد يسمى بـ "الخصائص المرفقة" ، وهي خصائص يمكن تطبيقها على العديد من العناصر رغم انها معرفة اصلا في فئات مختلفة.

عادة ما تستخدم الخصائص المرفقة من خلال الواصفات حيث يكون اسم الواصفة على الشكل OwnerType.PropertyName وهذه الصيغة تشبه إلى حد كبير الأسم في "صيغة العنصر الخاصية" لكن الفئة المالكة للخاصية هنا (OwnerType) غالبا ما تكون مختلفة عن فئة العنصر الكائن.

تستخدم الخصائص المرفقة في WPF كثيرا مع الواح التخطيط (Layout Panels) مثل Grid و DockPanel.

مثلا من اجل تحديد رقم الصف الذي سوف يوضع فيه العنصر داخل Grid فإننا نستخدم الخاصية المرفقة Grid.Row:

<Button Grid.Row="1" Content="Click Me"/>

لا تقوم الفئة المالكة للخاصية المرفقة بتعريف خاصية مقابلة بداخلها ، انما تقوم بتعريف دالة لتعيين القيمة تكون على شكل SetPropertyName() ودالة اخرى لأحضار القيمة تكون على شكل GetPropertyName().

مثلا الخاصية المرفقة Grid.Row يتم تحقيقها بهذا الشكل:

Grid = public class
    public class method GetRow(element: UIElement): Integer;
    public class method SetRow(element: UIElement; value: Integer);
  end;

كما يمكننا تعيين اي خاصية مرفقة من خلال الكود على هذا النحو:

Grid.SetRow(btn, 1);

تمديدات الوسوم (Markup Extensions)

من خلال الواصفات او العناصر الخاصية نقوم بتعيين خاصية الكائن إلى قيمة جديدة دائما.

بعض الحالات تتطلب منا تعيين الخاصية إلى قيمة موجودة من قبل ، او تعيينها إلى القيمة الخالية (null)، او ربط الخاصية بخاصية اخرى في كائن آخر ، ومثل هذه العمليات وغيرها يمكن إنجازها من خلال ما يسمى بـ" تمديدات الوسوم" (Markup Extensions) .

جميع التمديدات مشتقة من الفئة MarkupExtension (في المجال System.Windows.Markup)، وغالبا ما يكون اسمها منتهيا باللاحقة Extension التي يمكن اهمالها مع XAML.

غالبا ما تستخدم تمديدات الوسوم (Markup Extensions) مع الواصفات ، حيث يكون تمديد الوسم محاطاً بمربعات متعرجة { }.

المثال التالي على بعض تمديدات الوسوم (Markup Extensions):

<Button Background="{x:Null}" 
		Height="{x:Static SystemParameters.IconHeight}" 
		Content="{Binding Path=Height, RelativeSource={RelativeSource Self}}"/>

x:Null

التعيين إلى القيمة الخالية (null). حيث قمنا في هذا المثال بتعيين الخاصية Background إلى القيمة الخالية.

x:Static

التعيين إلى خاصية ساكنة في فئة في الدوت نت. في المثال قمنا بتعيين ارتفاع الزر إلى الخاصية الساكنة IconHeight في الفئة SystemParameters.

Binding

من اجل عمليات الربط (Data Binding) في WPF .

وكما نرى فإن هناك تمديدات تنتمي إلى XAML (تبدأ بـ x) واخرى تنتمي إلى WPF، كما يمكننا تعريف تمديدات مخصصة من خلال الأشتقاق من الفئة MarkupExtension.

ملاحظة:

إذا اردنا ان نعين الواصفة إلى نص يبدأ بقوس الأفتتاح { ، فإن علينا استخدام حروف الهروب.

مثلا لو اردنا تعيين الخاصية Content في الزر إلى النص "{some text}" فإننا نكتبها بهذه الطريقة :

<Button Content="{}{some text}"/>

ولا نحتاج إلى اي حروف هروب مع العنصر الخاصية:

<Button>
	{some text}
</Button>

الأحداث

يمكننا من خلال الواصفات ايضاً تحديد الإجراء او الدالة المعالجة لحدث في عنصر معين. وتكون الصيغة بهذا الشكل:

<Button ... Click="Button_Click ">

ويجب ان يحتوي الكود(Code-Behind) على الدالة Button_Click يتفق توقيعها مع توقيع الحدث:

method Window1.Button_Click(sender: System.Object; e: System.Windows.RoutedEventArgs);
begin
     MessageBox.Show("coucou");
end;

يقدم فيجوال استديو بعض الأدوات المساعدة (IntelliSense) مع الأحداث، فبمجرد ان تكتب اسم الحدث متبوعا بعلامة يساوي ، فإنه تظهر قائمة تتضمن إنشاء دالة حدث جديدة او بعض الدوال الأخرى الموجودة مسبقا .

لاحظ اننا نستخدم الواصفات من اجل تعيين الخصائص و الأحداث في الكائن، خلال عملية الأعراب يتم تعيين الأحداث قبل الخصائص في الكائن، باستثناء الخاصية Name التي يتم تعينها اولا(في حال استخدمت فعلاً)، ما يعني ان اي حدث مرتبط بتغيرات خاصية (مثل TextChanged ) سوف يقدح خلال هذه العملية.

استخدام فئات من مجالات اسماء اخرى:

إلى جانب التصريح عن مجال الأسم من خلال URI ، يوجد نوع آخر من التصريح يستخدم من اجل تحديد مجال اسم بعينه في الدوت نت .

وتكون صيغته بهذا الشكل:

xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName"

حيث:

- Prefix: تمثل البادئة التي نريد استخدامها عند التعامل مع الكائنات التي تنتمي لهذا المجال.

- Namespace: يمثل مجال الأسم الذي نريد التصريح عنه.

- AssemblyName: اسم التجمع الذي يحتوي على مجال الأسم. يمكننا اهمال هذا الجزء إذا كان المجال ينتمي إلى المشروع الحالي.

عادة ما يتم التصريح عن المجالات في العنصر الجذر في المستند.

مثلا من اجل التصريح عن المجال MyNamespace في المشروع الحالي:

xmlns:local="clr-namespace:MyNamespace"

ومن اجل إنشاء كائن في المجال MyNamespace:

<local:MyObject ...></local:MyObject>

ومن اجل التصريح عن المجال System في الدوت نت:

xmlns:sys="clr-namespace:System;assembly=mscorlib"

ومن اجل إنشاء كائن من النوع DateTime :

<sys:DateTime>10/30/2010 4:30 PM</sys:DateTime>

-------------------------------------------------------------------------------------------------

التخطيط في WPF

-------------------------------------------------------------------------------------------------

في البداية

يجب ان نعرف بإن المقصود بـ Layout هو عملية تحديد حجم وموقع كل عنصر في واجهة المستخدم بحيث ينتج الشكل النهائي المطلوب لتلك الواجهة. وهناك العديد من الكلمات العربية المرادفة لكلمة Layout مثل تخطيط ، تنظيم، تشكيل . وفي كل الأحوال اخترت في هذه المشاركة ان تكون كلمة تخطيط هي الكلمة المرادفة لـ Layout واعتذر ان كنت قد اخطأت في هذا الأختيار ، ايضاً هناك العديد من الكلمات الأنجليزية الأخرى التي لم استطع تحديد الكلمة العربية المرادفة لها ، او لا توجد كلمة عربية متفقا عليها ، لذا تجدني كثيرا ما اضع الكلمة العربية وبجوارها الكلمة الأنجليزية بين قوسين.

افترض ان لديك:

- خلفية ولو بسيطة عن WPF و XAML

- فيجوال استديو 2008

لماذا كتبت عن هذا الموضوع؟

ومن وجهة نظري الشخصية ، ان موضوع التخطيط (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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	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>

النتيجة:

post-173477-1250596780_thumb.jpg

مثال2:

نفس المثال السابق مع تغيير اتجاه التكديس.

<Window x:Class="SimpleLayout.Window1"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	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>

النتيجة:

post-173477-1250597135_thumb.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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	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>

النتيجة:

post-173477-1250596881_thumb.jpg

عندما نقوم بتغيير حجم النافذة فإن العناصر سوف تظهر بنفس الشكل تلقائيا.

فعند تكبير النافذة:

post-173477-1250597222_thumb.jpg

وعند تصغير النافذة:

post-173477-1250597279_thumb.jpg

لاحظ ايضاً انه لوقمنا بتغيير المحاذاة الرأسية في هذا المثال، فإنه لا يكون لها اي تأثير على العناصر، فعندما يكون اتجاه التكديس رأسي (Orientation تساوي Vertical) في StackPanel، فإنها تقوم بأعطاء كل عنصر الأرتفاع الذي يريده تماما.

وبنفس الطريقة ايضاً فإن المحاذاة الأفقية لا يكون لها اي تأثير على العناصر عندما يكون اتجاه التكديس افقي لأن StackPanel تعطي كل عنصر العرض الذي يريده تماماً.

الهوامش(Margin)

لو عدنا إلى المثال1 ، سوف نجد ان العناصر تلتصق ببعضها البعض، وقد يكون من الأفضل لو اننا استطعنا ان نضع فراغ بسيطا بينها . وهنا يأتي دور الخاصية Margin، فعن طريقها يمكن تحديد الهامش المطلوب لأي جانب من جوانب العنصر الأربعة (يسار، اعلى، يمين ، اسفل) .

توجد مرونة كبيرة في تحديد قيمة الخاصية Margin ، حيث يمكننا اعطاء نفس الهامش لكل الجوانب دفعة واحدة بالشكل التالي:

<Button Margin="5">Button 3</Button>

او تعيين الجوانب الأفقية (left و right) بهامش معين ، والجوانب الرأسية (top و bottom) بهامش آخر :

<Button Margin="5,10">Button 3</Button>

كما يمكننا اعطاء هامش مختلف لكل جانب وفق الترتيب left، top، right، bottom :

<Button Margin="5,10,5,10">Button 3</Button>

وتعيين الهوامش من خلال الكود C# يكون بالشكل التالي:

button1.Margin = new Thickness(5);
//Or
button1.Margin = new Thickness(5, 5, 5, 5);

تذكر دائما ان الهوامش بين العناصر تكون تراكمية ، فإذا وضعنا الزر 2 اسفل الزر 1 وكان الهامش السفلي للزر رقم 1 يساوي 5 والهامش العلوي للزر 2 ايضاً يساوي 5 فإن الهامش النهائي بين الزرين سيكون مساويا لـ 10.

مثال 4:

<Window x:Class="SimpleLayout.ElementsMargin"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Title="الهوامش- Margin" Height="300" Width="300">
	<StackPanel Margin="3">
		<Button Margin="3">Button 1</Button>
		<Button Margin="3">Button 2</Button>
		<Button Margin="3">Button 3</Button>
		<Button Margin="3">Button 4</Button>
	</StackPanel>
</Window>

النتيجة:

post-173477-1250597586_thumb.jpg

والشكل التالي يوضح كيف تتراكم الهوامش:

post-173477-1250597661_thumb.gif

خصائص الحجم:

كل عنصر في WPF لديه الخاصتين Width و Height والتي تسمح لنا بتحديد عرض وارتفاع العنصر بشكل صريح ومباشر. وكما ذكرنا سابقا ان مثل هذه التحديد غير مفضل في WPF .

إذا كان من الضروري تقييد او حصر حجم العنصر فإنه من الأفضل استخدام الخصائص :

- MinWidth و MinHeight من اجل تحديد الحد الأدنى لعرض وارتفاع العنصر.

- MaxWidth و MaxHeight من اجل تحديد الحد الأعلى لعرض وارتفاع العنصر.

مثال5:

لنفرض في مثال "المكدس والأزرار" انه قد نقرر ان الأزرار يجب ان تتسع باتساع StackPanel لكن بشرط ان لا يزيد عرضها عن 200 وحدة ولا يقل عن 100 وحدة .

<Window x:Class="SimpleLayout.ElementsSize"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Title="حجم العناصر" Height="300" Width="300">
	<StackPanel Margin="3">
		<Label Margin="3" HorizontalAlignment="Center">
			A Button Stack</Label>
		<Button Margin="3" MaxWidth="200" MinWidth="100">Button 1</Button>
		<Button Margin="3" MaxWidth="200" MinWidth="100">Button 2</Button>
		<Button Margin="3" MaxWidth="200" MinWidth="100">Button 3</Button>
		<Button Margin="3" MaxWidth="200" MinWidth="100">Button 4</Button>
	</StackPanel>
</Window>

النتيجة:

post-173477-1250597775_thumb.jpg

post-173477-1250597804_thumb.jpg

التداخل في الألواح:

كما ذكرنا سابقاً انه يمكن لأي لوح ان يحتوي على لوح آخر ، ويقوم اللوح الأب بالتعامل مع اللوح الأبن بنفس الطريقة التي يتعامل بها مع اي عنصر ابن آخر.

واستخدام اكثر من لوح هو امر شائع في WPF من اجل إنشاء التخطيط المطلوب، فعلى سبيل المثال لو اردنا إنشاء مربع حوار يتكون من زرين موافق والغاء الأمر يكونان في الأسفل ومربع نص يشغل المساحة المتبقية في النافذة فإننا نستخدم:

<DockPanel LastChildFill="True">
		<StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Orientation="Horizontal">
			<Button Margin="10,10,2,10" Padding="3">OK</Button>
			<Button Margin="2,10,10,10" Padding="3">Cancel</Button>
		</StackPanel>
		<TextBox DockPanel.Dock="Top" Margin="10">This is a test.</TextBox>
	</DockPanel>

النتيجة

post-173477-1250886392_thumb.png

استخدام Grid:

يعتبر هذا اللوح من افضل الألواح على الأطلاق ويمكننا بواسطته عمل كل شئ يقوم به اي لوح آخر(تقريباً).

يقوم هذا اللوح بتقسيم المساحة المعطاة له إلى صفوف واعمدة غير مرئية ، ويمكن وضع العناصر داخل الخلايا (الناتجة من تقاطع الصف مع العمود) . والجميل هنا انه يمكن وضع اكثر من عنصر في نفس الخلية، وبشكل افتراضي كل Grid يتكون من صف واحد وعمود واحد ، ما يعني بانه يتكون من خلية واحدة بشكل افتراضي.

من اجل اضافة الصفوف و الأعمدة إلى Grid ، فإن علينا استخدام الخاصيتين Grid.RowDefinitions و Grid.ColumnDefinitions ، فلو اردنا اضافة صفين وثلاثة اعمدة فإننا نستخدم:

<Grid>
  <Grid.RowDefinitions>
	 <RowDefinition/>
	 <RowDefinition/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
	 <ColumnDefinition/>
	 <ColumnDefinition/>
	 <ColumnDefinition/>
  </Grid.ColumnDefinitions>
</Grid>

تكون الصفوف متساوية في الأرتفاع ، والأعمدة متساوية في العرض بشكل افتراضي.

ملاحظة:

لاحظ انه يجب استخدام العنصر Grid.RowDefinitions ، والعنصر Grid.ColumnDefinitions من اجل تحديد الصفوف والأعمدة المطلوبة. تسمى هذه الصيغة في Xaml بـ "صيغة العنصر الخاصية" (Property Element Syntax) وهي طريقة اخرى لتعيين الخصائص في الكائن إلى جانب "صيغة الواصفة" (Attribute Syntax) .

بعد ذلك يمكننا إضافة العناصر إلى Grid وتحديد الصف والعمود الذي يقع فيه كل عنصر عن طريق الخصائص المرفقة Grid.Row و Grid.Column:

<Grid>
………
	<Button Grid.Row="0" Grid.Column="0">Top Left</Button>
	<Button Grid.Row="0" Grid.Column="1">Middle Left</Button>
	<Button Grid.Row="1" Grid.Column="2">Bottom Right</Button>
	<Button Grid.Row="1" Grid.Column="1">Bottom Middle</Button>
………
</Grid>

الصفوف والأعمدة في Grid ترقم على الأساس الصفري ، بمعنى ان الصف الأول يكون رقمه 0 والصف الثاني يكون رقمه 1 وهكذا.

إذا لم نقم بتحديد الصف او العمود لعنصر معين فإن هذا العنصر يتم وضعه في الصف الأول والعمود الأول ، بسبب ان القيمة الأفتراضية للخصائص المرفقة Grid.Row و Grid.Column هي صفر(بمعنى الأول).

post-173477-1250886558_thumb.png

حجم الأعمدة والصفوف:

تقدم Grid ثلاث طرق لتحديد ارتفاع الصف وعرض الأعمدة:

- الحجم المطلق (Absolute size): حيث يتم تعيين رقم يمثل عدد الوحدات التي يشغلها العرض او الأرتفاع

<ColumnDefinition Width="100"/>

- الحجم التلقائي(Automatic size): حيث يعطى الصف او العمود القدر الذي يحتاج اليه تماما (استنادا على حجم المحتوى)

 <ColumnDefinition Width="Auto"/>

- الحجم النسبي(Proportional size): حيث يتم تقسيم المساحة المعطاة على الصفوف اوالأعمدة

 <ColumnDefinition Width="*"/>

وبشكل افتراضي، يكون التناسب متساويا ما لم نقم بإضافة رقم معين يسبق النجمة مثل:

<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>

بهذا تكون نسبة الصف الأول إلى الصف الثاني هي 1 إلى 2.

عمل مربع الحوار السابق باستخدام Grid:

باستخدام هذه الطرق في التحجيم فإنه يمكننا استخدام Grid من اجل إنشاء نفس مربع الحوار السابق على النحو التالي :

<Grid ShowGridLines="True">
		<Grid.RowDefinitions>
			<RowDefinition Height="*"></RowDefinition>
			<RowDefinition Height="Auto"></RowDefinition>
		</Grid.RowDefinitions>
		<TextBox Margin="10" Grid.Row="0">This is a test.</TextBox>
		<StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
			<Button Margin="10,10,2,10" Padding="3">OK</Button>
			<Button Margin="2,10,10,10" Padding="3">Cancel</Button>
		</StackPanel>
	</Grid>

الخاصية ShowGridLines:

يمكن عرض خطوط الشبكة في Grid بتعيين هذه الخاصية إلى True (كما يظهر في المثال السابق).

الخصائص المرفقة Grid.RowSpan و Grid.ColumnSpan

يمكننا ان نجعل العنصر يشغل اكثر من صف واحد او عمود واحد من خلال الخصائص Grid.RowSpan و Grid.ColumnSpan، فعلى سبيل المثال لو اردنا ان يكون الزر في الصف الأول والعمود الأول ويشغل صفين وعمودين فإننا نكتب :

<Button Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2">Span Button</Button>

تقسيم النوافذ باستخدام GridSplitter

باستخدام GridSplitter يمكننا ان نجعل المستخدم قادرا على تغيير عرض العمود او ارتفاع الصف بنفس الطريقة التي تظهر في نوافذ مثل Windows Explorer حيث يكون بالأمكان تغيير عرض قائمة المجلدات عن طريق سحب حافتها في الأتجاه المطلوب.

post-173477-1250886672_thumb.png

إضافة GridSplitter إلى Grid عملية سهلة رغم ان الطريقة غريبة بعض الشئ،

ومن الجيد ان نتذكر التالي:

- يجب وضع GridSplitter داخل خلية في Grid ، هذه الخلية يمكن ان تحتوي على عناصر اخرى وفي هذه الحالة يجب ان نتأكد من هذه العناصر لا تقوم بإخفاء GridSplitter (بمعنى تغطيه) عن طريق تعيين الهوامش (Margin). والأفضل ان نقوم بعمل عمود او صف مخصص لـ GridSplitter

- لا يقوم GridSplitter بتعديل حجم الخلية فقط انما يقوم بتعديل حجم الصف او العمود بالكامل، ولكي نجعل هذا السلوك ينسجم مع المظهر في UI فإن علينا ان نجعل GridSplitter يمتد على طول الصف او العمود المطلوب باستخدام الخصائص Grid.RowSpan و Grid.ColumnSpan.

- مبدأيا يكون GridSplitter غير مرئيا ، ومن اجل ان نجعله مرئيا فلا بد من اعطائه حجم معين ينسجم مع الأتجاه الذي يعمل عليه. عن طريق اعطائه عرض معين (مع الأعمدة) او طول معين (مع الصفوف).

- تحدد محاذاة GridSplitter طريقة عمله ، بمعنى هل سيعمل كمقسم افقي (لتغيير حجم الصفوف) او كمقسم رأسي (لتغيير حجم الأعمدة) ، فمن اجل ان يعمل كمقسم افقي فإنه يجب تعيين الخاصية VerticalAlignment إلى Center بحيث يتم تغيير حجم الصف في الأعلى والصف في الأسفل. ومن اجل ان يعمل كمقسم رأسي فإنه يجب تعيين الخاصية HorizontalAlignment إلى Center بحيث يتم تغيير حجم العمود في اليسار والعمود في اليمين. (يمكن ايضاً استخدام الخصائص ResizeDirection و ResizeBehavior في GridSplitter من اجل تحديد السلوك المطلوب)

المثال التالي يوضح كيفية عمل مقسم رأسي لتغيير حجم الأعمدة :

<Grid>
		<Grid.RowDefinitions>
			<RowDefinition></RowDefinition>
			<RowDefinition></RowDefinition>
		</Grid.RowDefinitions>
		<Grid.ColumnDefinitions>
			<ColumnDefinition MinWidth="100"></ColumnDefinition>
			<ColumnDefinition Width="Auto"></ColumnDefinition>
			<ColumnDefinition MinWidth="50"></ColumnDefinition>
		</Grid.ColumnDefinitions>
		<Button Grid.Row="0" Grid.Column="0" Margin="3">Left</Button>
		<Button Grid.Row="0" Grid.Column="2" Margin="3">Right</Button>
		<Button Grid.Row="1" Grid.Column="0" Margin="3">Left</Button>
		<Button Grid.Row="1" Grid.Column="2" Margin="3">Right</Button>
		<GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="2"
			Width="3" VerticalAlignment="Stretch" HorizontalAlignment="Center"
			ShowsPreview="False"></GridSplitter>
</Grid>

post-173477-1250886672_thumb.png

الخاصية ShowsPreview :

تكون قيمة الخاصية ShowsPreview في GridSplitter هي False بشكل افتراضي ، مايعني ان GridSplitter سوف يقوم بتغيير حجم العمود او الصف مباشرة خلال عملية السحب، وعند تغيير هذه الخاصية إلى True فإن GridSplitter سوف يسمح للمستخدم ان يقوم بمعاينة الحجم المحدد اولا وعند تحرير زر الماوس فإن الحجم الجديد يتم تطبيقه.

شكرا شكرا استاذنا HassanAlattas

تم تعديل هذه المشاركة بواسطة زكيري عبد العالي في 18 يونيو 2011 في 19:17

مواضيع مشابهة