6. إدارة الأحداث
لقد تناولنا في الفصل السابق مفهوم الأحداث المرتبطة بالمكونات. سنرى الآن كيفية إنشاء الأحداث في فئاتنا الخاصة. هذا مفهوم صعب ولن يثير اهتمام المطورين المبتدئين، الذين يُنصح بالانتقال مباشرةً إلى الفصل التالي.
6.1. كائنات الـ delegate
التعليمة
نوعًا يُسمى opération، وهو في الواقع نموذج أولي لدالة تقبل عددين صحيحين وتُرجع عددًا صحيحًا. الكلمة المفتاحية delegate هي التي تجعل من opération تعريفًا لنموذج أولي لدالة. يمكن تعيين النوع [opération] إلى متغير بالطريقة التالية:
Dim op As New opération(AddressOf [fonction])
يتم إنشاء متغير من النوع [délégué] ككائن. المعلمة الخاصة بالمنشئ هي الدالة المرتبطة بمثيل الوكيل. في VB، تُعطى هذه الدالة في صيغة AddressOf دالة، حيث [fonction] هو اسم الدالة. بمجرد إنشاء مثيل الوكيل، يمكن استخدامه كدالة:
' تنفيذ الدالة المفوضة
Dim n As Integer = op(4, 7)
تم تعريف الوكيل [opération] بحيث يتلقى معلمتين من النوع [integer] ويعيد نتيجة من نفس النوع. لذا، تُستخدم مثيله [op] المذكور أعلاه كدالة من هذا النوع. لننظر إلى المثال التالي:
' الوظائف المفوضة
Imports System
Public Class delegate1
' تعريف نموذج أولي لدالة
' تقبل عدديْن صحيحيْن كمعلمات وتُرجع عددًا صحيحًا
Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
' طريقتان للمثيل تتوافقان مع النموذج الأولي
' إضافة
Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
Return n1 + n2
End Function
' طرح
Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
Return n1 - n2
End Function
' طريقة ثابتة تتوافق مع النموذج الأولي
Public Shared Function augmenter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("augmenter(" & n1 & "," & n2 & ")"))
Return n1 + 2 * n2
End Function
' برنامج اختبار
Public Shared Sub Main()
' يتم تعريف كائن من نوع «عملية» لتسجيل الدوال فيه
' يتم تسجيل الدالة الثابتة «زيادة»
Dim op As New opération(AddressOf delegate1.augmenter)
' يتم تنفيذ الوكيل
Dim n As Integer = op(4, 7)
Console.Out.WriteLine(("n=" & n))
' إنشاء كائن c1 من نوع class1
Dim c1 As New delegate1
' يتم تسجيل الأسلوب «إضافة» الخاص بـ c1 في الوكيل
op = New delegate1.opération(AddressOf c1.ajouter)
' تنفيذ الكائن الوكيل
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
' يتم تسجيل طريقة «طرح» الخاصة بـ c1 في الوكيل
op = New delegate1.opération(AddressOf c1.soustraire)
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
End Sub
End Class
نتائج التنفيذ هي كما يلي:
6.2. إدارة الأحداث
ما هي وظيفة الكائن من النوع delegate؟
وكما سنرى في المثال التالي، فإن هذا يُستخدم بشكل أساسي لإدارة الأحداث. يمكن لفئة C1 أن تولد أحداثًا من نوع evti. عند وقوع حدث من النوع evti، سيقوم كائن من النوع C1 بتشغيل كائن من النوع evtiDéclenché الذي ينتمي إلى النوع delegate. وعندئذٍ سيتم تنفيذ جميع الوظائف المسجلة في الكائن الوكيل evtiDéclenché. إذا أراد كائن C2 الذي يستخدم كائن C1 أن يتم إخطاره بحدوث الحدث evti على الكائن C1، فسيقوم بتسجيل إحدى طرقه C2.f في الكائن المفوض C1.evtiDéclenché التابع للكائن C1، بحيث يتم تنفيذ الطريقة C2.f في كل مرةيحدث الحدث evti على الكائن C1. ونظرًا لأن الكائن المفوض C1.evtiDéclenché يمكنه تسجيل عدة وظائف، فإن كائنات Ci المختلفة ستتمكن من التسجيل لدى الوكيل C1.evtiDéclenché لتلقي إشعار بالحدث evti على C1.
6.2.1. الإعلان عن حدث
يتم الإعلان عن الحدث بالطريقة التالية (1):
Delegate Sub proc(liste d'arguments)
Public Event événement as proc
أو (2)
Public Event événement(liste d'arguments)
الكلمة الرئيسية [Event] هي التي تُعرّف البيانات على أنها حدث. ويُربط بالحدث التوقيع الذي يجب أن يتوفر فيه معالجات هذا الحدث. ويمكن تعريف هذا التوقيع عن طريق مندوب (الطريقة 1)، أو بشكل مباشر (الطريقة 2).
6.2.2. تحديد معالجات الحدث
عندما يتم تشغيل حدث بواسطة الأمر [RaiseEvent]، يجب معالجته. تُسمى الإجراءات المكلفة بمعالجة الأحداث «معالجات الأحداث». عند إنشاء كائن [Event]، يتم إعلان توقيع معالجات الحدث فقط. وهذا التوقيع هو توقيع الكائن [delegate] المرتبط بالحدث. وبعد ذلك، يصبح من الممكن ربط معالجات بالحدث. وهذه المعالجات هي إجراءات يجب أن يكون لها نفس التوقيع الخاص بالكائن [delegate] المرتبط بالحدث. لربط معالج حدث بحدث ما، يتم استخدام الأمر [AddHandler] بالصيغة التالية:
- event: متغير الحدث الذي يتم ربط معالج أحداث جديد به
- eventhandler: اسم معالج الأحداث - يجب أن يكون له التوقيع الخاص بـ [délégué] المرتبط بالحدث
يمكن ربط أي عدد من معالجات الأحداث بحدث واحد. وسيتم تنفيذها جميعًا عند حدوث الحدث المرتبط بها.
6.2.3. إطلاق حدث
لتشغيل حدث برمجيًا، تُستخدم التعليمات [RaiseEvent]:
- eventname: متغير الحدث (يُعلن باستخدام الكلمة الرئيسية Event)
- argumentlist: معلمات الوكيل المرتبط بالحدث.
سيتم استدعاء جميع المديرين الذين تم ربطهم بالحدث بواسطة الأمر AddHandler. وسيتلقون كمعلمات المعلمات المحددة في توقيع الحدث.
6.2.4. مثال
لنأخذ المثال التالي:
'إدارة الأحداث
Imports System
Public Class myEventArgs
Inherits EventArgs
' فئة الحدث
' السمة
Private _saisie As String
' منشئ
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' خاصية الإدخال للقراءة فقط
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Public Class émetteur
' فئة مُرسِل الحدث
' السمة
Private _nom As String ' nom de l'émetteur
' منشئ
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' النموذج الأولي للوظائف المسؤولة عن معالجة الحدث
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' مجموعة معالجي الأحداث
Public Event evtHandler As _evtHandler
' طريقة طلب إصدار حدث
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' يتم إخطار جميع المشتركين
' يتم التعامل مع الحدث كما لو كان صادرًا من هنا
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
' فئة معالجة الحدث
Public Class souscripteur
' سمة
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' منشئ
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' نسجل اسم المشترك
Me.nom = nom
' ومصدر الأحداث
Me.sender = e
' يتم التسجيل لتلقي أحداث المُصدر e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' مدير الأحداث
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' عرض الأحداث
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
' برنامج اختبار
Public Class test
Public Shared Sub Main()
' إنشاء مصدر أحداث
Dim émetteur1 As New émetteur("émetteur1")
' إنشاء جدول بالمشتركين
' للأحداث الصادرة عن المرسل 1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' قراءة سلسلة من الأعداد الصحيحة من لوحة المفاتيح
' بمجرد وجود خطأ في أحد الأرقام، يتم إرسال حدث
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' طالما أن السطر المدخل غير فارغ
While saisie <> ""
' هل القيمة المدخلة عدد صحيح؟
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' إنه ليس عددًا صحيحًا
' يتم إخطار الجميع
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' يتم إخطار الجميع
' إدخال جديد
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' نهاية
Environment.Exit(0)
End Sub
End Class
الرمز السابق معقد إلى حد ما. دعونا نحلله بالتفصيل. في إدارة الأحداث، يوجد مُرسِل للأحداث (sender) يرسل تفاصيل الأحداث (EventArgs) إلى المشتركين الذين أبدوا اهتمامهم بالأحداث المعنية. فئة مُرسِل الأحداث هنا هي التالية:
Public Class émetteur
' الفئة المُصدرة لحدث
' السمة
Private _nom As String ' nom de l'émetteur
' المنشئ
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' النموذج الأولي للوظائف المسؤولة عن معالجة الحدث
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' مجموعة معالجي الأحداث
Public Event evtHandler As _evtHandler
' طريقة طلب إصدار حدث
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' يتم إخطار جميع المشتركين
' يتم التعامل مع الحدث كما لو كان صادرًا من هنا
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
يحتوي كل كائن émetteur على كائن nom محدد بشكل افتراضي. تمت إعادة تعريف الطريقة ToString بحيث يتم استرداد اسم الكائن عند تحويل كائن émetteur إلى string,. تحدد الفئة حدثًا:
' النموذج الأولي للوظائف المسؤولة عن معالجة الحدث
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' الحدث
Public Event evtHandler As _evtHandler
سيكون الحجة الأولى لدالة معالجة الحدث هي الكائن الذي يصدر الحدث. أما الحجة الثانية فستكون من النوع myEventArgs، وهو كائن يقدم تفاصيل عن الحدث وسنعود إليه لاحقًا. ولتمكين تشغيل حدث من خارج كائن émetteur، نضيف إلى الفئة الطريقة envoyerEvt:
' طريقة طلب إصدار حدث
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' يتم إخطار جميع المشتركين
' يتم التعامل مع الحدث كما لو كان مصدره من هنا
RaiseEvent evtHandler(Me, evt)
End Sub
علينا تحديد نوع الأحداث التي ستُطلقها الفئة émetteur. ولهذا الغرض، قمنا بتعريف الفئة myEventArgs:
Public Class myEventArgs
' فئة الحدث
' السمة
Private _saisie As String
' منشئ
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' خاصية إدخال للقراءة فقط
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
الأحداث التي تهمنا هي الإدخالات الخاطئة عبر لوحة المفاتيح. سنطلب من المستخدم إدخال أعداد صحيحة عبر لوحة المفاتيح، وبمجرد أن يدخل سلسلة لا تمثل عددًا صحيحًا، سيتم تشغيل حدث. كبيانات تفصيلية للحدث، سنكتفي بتقديم الإدخال الخاطئ. وهذا هو الغرض من السمة _saisie للفئة. وبالتالي، يتم إنشاء كائن myEventArgs مع الإدخال الخاطئ كمعلمة. كما نُعيد تعريف الطريقة ToString بحيث عندما يتم تحويل كائن myEventArgs إلى سلسلة، نحصل على السمة _saisie.
لقد حددنا مصدر الأحداث ونوع الأحداث التي يرسلها. ثم نحدد نوع المشتركين المهتمين بهذه الأحداث.
' فئة معالجة الحدث
Public Class souscripteur
' السمة
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' الشركة المصنعة
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' يُذكر اسم المشترك
Me.nom = nom
' ومصدر الأحداث
Me.sender = e
' يتم التسجيل لتلقي الأحداث من مصدر الأحداث e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' مدير الأحداث
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' عرض الأحداث
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
يتم تعريف المشترك بواسطة معلمتين: اسمه (السمة nom) والكائن émetteur الذي يرغب في معالجة أحداثه (السمة sender). سيتم تمرير هاتين المعلمتين إلى منشئ الكائن. وأثناء عملية الإنشاء هذه نفسها، يشترك المشترك في أحداث المُصدر:
' يتم التسجيل لتلقي الأحداث من المرسل e
AddHandler e.evtHandler, AddressOf traitementEvt
الدالة المسجلة لدى المُرسِل هي traitementEvt. تعرض هذه الطريقة التابعة للفئة souscripteur الحجتين اللتين تلقتهما (sender، evt) بالإضافة إلى اسم المستقبل (nom). تم تحديد نوع الأحداث التي تم إنتاجها، ونوع مصدر هذه الأحداث، ونوع المشتركين. ولم يتبق لنا سوى تنفيذها:
' برنامج اختبار
Public Class test
Public Shared Sub Main()
' إنشاء مصدر أحداث
Dim émetteur1 As New émetteur("émetteur1")
' إنشاء قائمة بالمشتركين
' للأحداث الصادرة عن المُرسِل 1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' قراءة سلسلة من الأعداد الصحيحة من لوحة المفاتيح
' بمجرد وجود خطأ في أحد الأرقام، يتم إرسال حدث
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' طالما أن السطر المدخل غير فارغ
While saisie <> ""
' هل القيمة المدخلة عدد صحيح؟
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' إنه ليس عددًا صحيحًا
' يتم إخطار الجميع
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' يتم إخطار الجميع
' إدخال جديد
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' نهاية
Environment.Exit(0)
End Sub
نقوم بإنشاء كائن émetteur:
' إنشاء مُرسِل للأحداث
Dim émetteur1 As New émetteur("émetteur1")
نقوم بإنشاء مصفوفة من مشتركين اثنين للأحداث التي يصدرها الكائن émetteur1:
' إنشاء قائمة بالمشتركين
' للأحداث الصادرة عن المرسل 1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
نطلب من المستخدم إدخال أعداد صحيحة عبر لوحة المفاتيح. بمجرد حدوث خطأ في الإدخال، نطلب من émetteur1 إرسال حدث إلى المشتركين التابعين له:
' يتم إخطار الجميع
émetteur1.envoyerEvt(New myEventArgs(saisie))
الحدث المرسل هو من النوع myEventArgs ويحتوي على الإدخال الخاطئ. من المفترض أن يتلقى المشتركان هذا الحدث ويقومان بالإبلاغ عنه. وهذا ما يوضحه التنفيذ التالي.
dos>evt1
Nombre entier (rien pour arrêter) : 4
Nombre entier (rien pour arrêter) : a
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s2]
Nombre entier (rien pour arrêter) : 1.6
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s2]
Nombre entier (rien pour arrêter) :