تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
WPF الأساسيات : الأحداث المُوجهّة Routed Events
#1
تم الإشارة في مقال سابق, إلى أن WPF أضاف نوعًا جديدًا من الخصائص (خصائص التبعيّة Dependency Properties), هذا النوع قام بتوسيع مفهوم خصائص CLR الكلاسيكية والمعروفة بـ CLR Properties, من جهة أخرى أضاف WPF أيضًا نوعًا جديدًا من الأحداث Events, هذا النوع الجديد هو الآخر قام بتوسيع مفهوم الأحداث الكلاسيكية , هذا النوع من الأحداث يُسمّى Routed Events, أو الأحداث المُوجهّة. تُوضِّح هذه المقالة كيفية إنشاء Routed Events واستخددامها عمليًا, محتويات هذه المقالة كالتالي:
  • مقدِّمة
  • كيف يُمكن كتابة الأحداث المُوجهّة؟
  • أساليب التوجيه Routing Strategies, ومُعالجات الأحداث Event Handlers
  • كيف يُمكن استخدام الاحداث المُوجهّة؟
  • الأحداث المُرفقة Attached Events
مقدِّمة

مفهوم الأحداث قديم ومألوف لدي المُطورّين, والذي يعتمد على إرسال رسائل من كائن ما (عنصر Button على سبيل المثال), والتي تدل على وقوع حدث مُميّز. تمّ تصميم الأحداث المُوجهّة من أجل العمل بشكل متكامل مع شجرة العناصر Elements Tree في WPF, عندما يتمّ إطلاق حدث مُوجّه, يُمكنه التنقل عبر شجرة العناصر إلى أعلى أو إلى أسفل, بحيث من المُمكن أن يتم إعادة إطلاقه مرة أخرى من أجل كل عنصر من عناصر تلك الشجرة بطريقة استثنائية دون الحاجة لكتابة شفرات مُخصّصة لذلك.

تعتبر الأحداث المُوجهة أحد أهم أسباب نجاح نموذج عناصر WPF, والسبب في ذلك, هو تكامل وظائفها خصوصًا عند التعامل مع تنسيقات المظهر Styles, كما أنها تسمح بكتابة شفرات أقل وأكثر تناسقًا. توجيه الأحداث يُمكِّن حدثًا ما من أن يصدر من عنصر مُحدّد, ويتم إطلاقه في عنصر آخر, على سبيل المثال, عند النقر على Button في أداة Toolbar فإن الحدث Click يتم إطلاقه انطلاقًا من ذلك الـ Button, ثمّ من أداة Toolbar, ثمّ من الإطار Window , وذلك قبل أن تتمّ مُعالجته من خلال شفراتك!

كيف يُمكن كتابة الأحداث المُوجهّة؟

كيفية تعريف الأحداث المُوجهّة شبيهة إلى حد كبير بكيفية تعريف خصائص التبعيّة Dependency Properties, وكما يحدث غالبًا مع خصائص التبعيّة, فإن الأحداث المُوجهّة يتم إنشاؤها بالاستعانة بحقول مشتركة Static fields يتم تسجيلها في مُنشئ مثشترك Static constructor , ثم يتمّ تغليفها باستخدام الأحداث الكلاسيكية, والمثال التالي المأخوذ من تعريف الفئة ButtonBase يُوضِّح ذلك:
PHP كود :
public abstract class ButtonBase ContentControl, ... {     // تعريف الحدث    public static readonly RoutedEvent ClickEvent;      // تسجيل الحدث     static ButtonBase()     {         ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent(           "Click", RoutingStrategy.Bubble,           typeof(RoutedEventHandler), typeof(ButtonBase));         ...     }      // التغليف باستخدام الأحداث الكلاسيكية    public event RoutedEventHandler Click     {         add         {             base.AddHandler(ButtonBase.ClickEvent, value);         }         remove         {             base.RemoveHandler(ButtonBase.ClickEvent, value);         }     }      ... } 
بينما يتمّ عادةُ تسجيل خصائص التبعيّة باستخدام الإجراء DependencyPropert.Register, يتمّ تسجيل الأحداث المُوجهّة باستخدام الإجراء EventManager.RegisterRoutedEvent, وعندما تقوم بتسجيل حدث مُوجّه تحتاج إلى تحديد اسم الحدث, أسلوب التوجيه الذي سيتمّ الحديث عنه لاحقًا ضمن هذا المقال (RoutingStrategy.Bubble في هذه الحالة), ونوع مُعالج الحدث (RoutedEventHandler في هذه الحالة), ونوع الفئة الحاوية لهذا الحدث (ButtonBase في هذه الحالة).

عادةُ ما يتمّ تغليف الأحداث المُوجهّة باستخدام الأحداث الكلاسيكية, وذلك حتى يسهُل للغات البرمجة الوصول إليها, عند تغليف الأحداث المُوجهّة يتمّ استخدام الإجرائين AddHandler و RemoveHandler , والذين يقومان بتسجيل Subscribing أو إلغاء تسجيل Unsubscribing لمُتلقيي الأحداث Callers, كلا الإجرائين السابقين موروثان من النوع FrameworkElement الذي ترث منه جميع عناصر WPF.

بالتأكيد, مثل أي حدث آخر, تحتاج لإطلاق الحدث الذي أنشأته من خلال الفئة التي قمت فيها بتعريف ذلك الحدث, ولكن بطريقة تختلف عن الطريقة الكلاسيكية التي تعوّدت عليها عند تعريف الأحداث الكلاسيكية, في WPF تحتاج لاستدعاء الإجراء RaiseEvent الموروث من النوع UIElement, والمثال التالي المأخوذ من أحد أجزاء تعريف الفئة ButtonBase يُوضّح كيفية استخدام هذا الإجراء:
PHP كود :
RoutedEventArgs e = new RoutedEventArgs(ButtonBase.ClickEventthis);base.RaiseEvent(e); 
الإجراء RaiseEvent سيتولّى إطلاق الحدث لكل Caller تمّ تسجيله من خلال الإجراء AddHandler, ولأن الإجراء AddHandler هو إجراء عام Public, فإن لديك الخيار إما بتسجيل Callers من خلال الإجراء AddHandler, وإما باستخدام طريقة التسجيل الكلاسيكية:
PHP كود :
btnClick.Click += delegate { ... }; 
وذلك لأن جميع عناصر WPF تعتمد على الطريقة المُتفق عليها في استخدام الأحداث, حيث أن الوسيط الأول لكلّ مُعالج حدث Event Handler يكون كائنُا من النوع object, ويُمثِّل ذلك الكائن مَصدر إطلاق الحدث Sender, والوسيط الثاني كائن من النوع EventArgs والذي قد يحمل أيّة معلومات إضافية تتعلّق بطبيعة ذلك الحدث, على سبيل المثال, الحدث MouseUp يُوفِّر كائنًا من النوع MouseEventArgs, والذي يحمل معلومات حول زر الماوس الذي قام المستخدم بضغطه.
PHP كود :
private void btn_MouseUp(object senderMouseButtonEventArgs e){ } 
في تطبيقات Windows Forms, عندما لا يكون هناك حاجة لتقديم أيّة معلومات إضافية لحدث ما, يتوفّر ذلك الحدث فقط على وسيط من النوع EventArgs, ولكن الأمر مُختلف في WPF, فمن أجل أن تدعم عناصر WPF الأحداث المُوجهّة, عليها استخدام الفئة RoutedEventArgs, والتي تُوفِّر معلومات إضافية مثلًا عن كيفية توجيه تلك الأحداث, إذا كانت هناك حاجة لتوفير معلومات إضافية, يجب تعريف الحدث منذ البداية ليستخدم مُعالج حدث EventHandler يستخدم وسيطة من نوع مُشتق من النوع RoutedEventArgs, مثلما هو الحال في النوع MouseButtonEventArgs في المثال السابق.

أساليب التوجيه Routing Strategies

العديد من عناصر WPF عبارة عن Content Controls, ما يعني أنّ لديها القدرة على احتواء أي عدد وأي نوع من العناصر المتداخلة, على سبيل المثال, يُمكنك بناء Button باستخدام Shapes, أو إنشاء Label يحتوي على مزيج من النصوص والصُّور, ويُمكنك حتى إنشاء عناصر مُتداخلة فيما بينها لعمق غير محدود! ولكن هذا التداخل قد يثير تساؤُلاً مُثيرًا للاهتمام, افترض أن لديك عنصر Label كما في المثال التالي, والذي يحتوي على StackPanel يحمل هو الآخر عنصري TextBlock وعنصر Image :
PHP كود :
<Label BorderBrush="Black"               BorderThickness="1">            <StackPanel>                <TextBlock Margin="3">                     Image and text label                </TextBlock>                <Image Source="happyface.jpg"                       Stretch="None" />                <TextBlock Margin="3">                     Courtesy of the StackPanel                </TextBlock>            </StackPanel>        </Label
العناصر السابقة تشترك في أنها كلّها مُشتقّة من النوع UIElement, هذا النوع يحتوي على أحداث مُوجهّة مثل MouseUp, و MouseDown. ولكن ماذا سيحدث لو قام المستخدم بالنقر بزر الماوس فوق العنصر Image , من الطبيعي أن يقوم العنصر Image بإطلاق الحدثين Image.MouseDownو Image.MouseUp , ولكن ماذا لو أردت أن تقوم بمُعالجة حدث النقر مهما كان العنصر الذي تم النقر عليه, سواءًا عنصر Label أو Image, أو حتى المساحة الفارغة بين حدود StackPanel و Label؟ في هذه الحالة لا يهّم ماهو العنصر, ولكن المهم أن تتمّ مُعالجة حدث النقر من خلال الشفرة بنفس الكيفية من أجل كلّ العناصر.



ربما قد يخطر ببالك أنه يجب عليك أن تقوم بمعالجة هذا الحدث من أجل كلّ عنصر موجود داخل عنصر Label السابق, ولكن بهذه الطريقة, ستصبح شفرات Xaml أكثر تعقيدًا, وأصعب فهمًا, لهذا من المُفيد الاعتماد على الأحداث المُوجهّة في WPF التي حلّت هذه المُشكلة, توجيه الأحداث يعتمد على 3 أساليب في إطلاق الأحداث ومُعالجتها:
  • الأسلوب المُباشر Direct: تُعتبر أحداث التي تتبّع هذا الأسلوب مثل الأحداث الكلاسيكيّة أي أن إطلاقها يكون من العنصر مَصدر الحدث مباشرةً, على سبيل المثال, الحدث MouseEnter الذي يتمّ إطلاقه فور دخول مُؤشّر الماوس داخل عنصر ما عبارة عن حدث مُباشر.
  • الأسلوب Bubbling: الأحداث التي تمّ تعريفها لاستخدام هذا الأسلوب, يتمّ إطلاقها بدءًا من العنصر مَصدر الحدث, ثم يتم إطلاقها مُجدّدًا من العنصر الأبوي Parent Element للعنصر الأول, ومن ثم يتمّ إطلاقها مُجدّدًا من العنصر الأبوي للعنصر الأبوي السابق, وهكذا حتى يصل WPF إلى أعلى عنصر في شجرة العناصر.
  • الأسلوب Tunneling: الأحداث التي تعتمد على هذا الأسلوب, تبتدأ تسميتُها بالكلمة Preview, على سبيل المثال, الحدث PreviewMouseDown, هذه الأحداث يتمّ إطلاقها بدءًا من أعلى عنصر في شجرة العناصر ووصولاُ إلى العنصر مَصدر الحدذ, الأمر الذي يجعلك قادرًا على منع وصول الحدث إلى الذي العنصر تم نقره على سبيل المثال, وكمثال آخر تستطيع منع وصول الحدث KeyDown إلى أداة TextBox من خلال الحدث PreviewKeyDown الذي يتم إطلاقه بدءًا من الإطار Window الموجود فيه العنصر TextBox حتى الوصول إلى TextBox مرورًا بجميع العناصر الأبوية Parent Elements .
عندما تقوم بتسجيل حدث مُوجّه باستخدام EventManager.RegisterRoutedEvent, يُمكنك تمرير قيمة من نوع الترقيم RoutingStartegy Enum والذي سيُحدّد السلوك الخاص الحدث الذي تقوم بتسجيله. لأن الحدثين MouseDown و MouseUp يتبعان الأسلوب Bubbling , فإنك عندما تقوم بالنقر على العنصر Image , سيتّم إطلاق الحدث MouseDown وفق الترتيب التالي:
  • Image.MouseDown
  • StackPanel.MouseDown
  • Label.MouseDown
عندما يتمّ إطلاق الحدث MouseDown من أجل العنصر Label سيتمّ تمريره ليتم إطلاقه من العنصر التالي (Grid في هذه الحالة باعتبار أن Label موجود بداخل Grid), ومن ثم إلى الإطار Window الحاوي لجميع العناصر بما فيها Grid, الإطار Window هو أعلى عنصر في شجرة العناصر وهو آخر عنصر يصل إليه الحدث من النوع Bubbling, لذلك فهو آخر عنصر مُتوفّر لديك حتى تتمكن من مُعالجة حدث مثل MouseDown. عندما يقوم المُستخدم بإفلات زر الماوس سيتمّ إطلاق الحدث MouseUp بنفس الكيفيّة السابقة.

في الواقع ليس هناك ما يُجبرك على مُعالجة الأحداث من النوع Bubbling مرّة واحدة فقط ومن أجل عنصر واحد, ولكن هذا من الأساليب الجيّدة دائمًا حتى تُقلّل من كميّة الشفرات اللزمة لذلك, وحتى تقوم أيضًا بمُعالجة الحدث المناسب وتختار من أجل ذلك العنصر الأقرب والأكثر ملائمةً!

الفئة RoutedEventArgs



عندما تُعالج حدثًا من النوع Bubbling, فإن الوسيط sender سيُشير إلى آخر عنصر وصل إليه الحدث, على سبيل المثال, عندما تُعالج حدثًا يصدر من العنصر Image (المثال السابق) نحو العنصر Label, فإن الوسيط sender سيُشير للعنصر Label. وفي بعض الحالات قد تحتاج إلى تحديد المَصدر الحقيقي للحدث, في هذه الحالة يُمكنك الحصول على تلك المعلومة وبعض المعلومات الإضافية من خلال الفئة RoutedEventArgs, كما أن جميع الأحداث الموجودة في عناصر WPF تعتمد على هذه الفئة أو إحدى الفئات الموروثة منها. وفيما يلي توضيح لأهم خصائص هذه الفئة ودور كلّ منها:
  • الخاصيّة Source: وتُحدّد العنصر الذي أطلق للحدث. في الحالة التي يكون فيها الحدث مُتعلقًا بلوحة المفاتيح keyboard, فإن المَصدر سيكون العنصر الذي أخذ التركيز "Focused" (كما هو الحال عند النقر على أحد مفاتيح لوحة المفاتيح وأنت بداخل العنصر TextBox), في الحالة التي يكون فيها الحدث مُتعلقًا بالماوس, فإن المَصدر سيكون العنصر الذي يقف مؤشر الماوس تمامًا عنده (كما هو الحال عند النقر بزر الماوس الأيمن).
  • الخاصيّة OriginalSource: وتُحدّد العنصر الرئيسي الذي أطلق الحدث. في العادة, ستحمل هذه الخاصيّة والخاصيّة Source نفس القيمة, ولكن في بعض الحالات ستكون الخاصيّة OriginalSource أكثر دقة من الأخرى, على سبيل المثال عندما تنقر قريبًا من حدود الإطار فغن الخاصيّة Source ستُشير إلى الإطار Window, ولكن الخاصيّة OriginalSource ستُشير إلى عنصر Border , وهذا لأن الإطار مُكوّن في الحقيقة من عناصر ثانوية , من بينها عنصر من نوع Border.
  • الخاصيّة RoutedEvent: تُعيد هذه الخاصيّة كائنًا من النوع RoutedEvent, مثل UIElement.MouseDownEvent, هذه الخاصيّة مُفيدة خصوصًا عندما تُعالج عدة أحداث باستخدام نفس مُعالج الحدث EventHandler.
  • الخاصيّة Handled: يُمكن من خلال هذه الخاصيّة إيقاف الحدث المُعالج من الاستمرار في المرور في شجرة العناصر, وعليه لن يتمّ إطلاقه من أجل باقي العناصر , ويتمّ ذلك من خلال إسناد القيمة True لها.

يُتبع ...
الرد }}}
تم الشكر بواسطة: السندبااد
#2
ممتاز جدا جدا
microsoft partner
Team administrator
MCPD,MCITP,OCP,MP,MCC
Xprema Systems
الرد }}}
تم الشكر بواسطة: farhat ali
#3
بارك الله فيك
الرد }}}
تم الشكر بواسطة: elgokr , السندبااد


المواضيع المحتمل أن تكون متشابهة .
الموضوع : الكاتب الردود : المشاهدات : آخر رد
  WPF الأساسيات : ربط البيانات DataBinding في WPF Islam Ibrahim 2 5,905 23-07-18, 02:48 AM
آخر رد: farhat ali

التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم