05-10-12, 06:20 PM
كاتب الموضوع : silverlight
بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته
مقدمـــــة:
من المؤكد أن قارئ هذا المقال فد سمع عن أو استخدم كلمة Nested Control ولكي نوضح للقارئ المعني المقصود من هذه الكلمة فإن أشهر الكونترول التي يمكن تصنيفها علي أنها Nested Control وعلي سبيل المثال لا الحصر هي كالأتي:
ListBox
ListView
DataGridView
وفي هذا المقال سنحاول أن نوضح كيف يتم صناعة مثل هذا النوع من الكونترول
أيضا في هذا الموضوع سوف نلقي نظرة علي استخدام وصناعة أشياء مثل EventArgs و EventHandler و Collections و IEnumerable و IEnumerator وبالتالي سيكون هذا المقال وجبة جيدة لفهم الكثير من الأمور الموجودة في الدوت نت والتي قلما تجد أحدا يكتب عنها وخاصة بلغة الفيجوال بيسك دوت نت.
ماذا نحتاج لنصنع هذا النوع من الكونترول
لكي نصنع هذا النوع من الكونترول يجب أن يكون لدينا فهم جيد جدا باللغة التي نتعامل معها وعلي سبيل المثال لا الحصر يجب أن يكون لدينا إدراك تام لكيفية التعامل مع المسميات التالية
Interface
Control
Component
Collection أو Generics
+GDI أو الجرافكس
CollectionEditor
EventArgs & EventHandler
قد يتساءل اليعض ماهي علاقة Interface بموضوعنا هذا؟
في واقع الأمر أنه وعند إضافة Interface الي اي كلاس أو Component ما فإنها وبالتبعية سوف تمتلك جميع الخصائص الموجودة في هذا Interface ومن هنا تكمن أهمية Interface ولمزيد من المعلومات عن Interface يمكنكم مراجعة اللينك التالي
Interface
كيف نصنع الكونترول
الحقيقة المؤكدة أن مايكروسوفت وعندما بدأت في صناعة مثل هذا النوع من الكونترول كانت تعتمد وبشكل كبير جدا علي استخدام دوال API المختلفة أو كانت تعتمد علي ما يمكن أن نطلق عليه Native Methods وقد يتبادر لذهن القارئ السؤال التالي:
هل نحن في حاجة لأن نتبع أو ننهج نفس الأسلوب الذي اتبعته مايكروسوفت في صناعة مثل هذا النوع من الكونترول أم لا؟
والإجابة وببساطة شديدة هي أننا لسنا في حاجة لأن نتبع نفس أسلوب مايكروسوفت ولكن علينا أن نستخدم التطور الجيد الموجود في الدوت نت ونستفيد منه بشكل يساعد علي بناء Nested Controls عموما وفي نهاية الأمر ........ أعتقد أن هذا الأمر غالبا متروك لرؤية المبرمج وخبراته الفردية.
عموما هنا وبدلا من استخدام دوال API يمكن أن نبني كونترول بسيط جدا ونستخدمه كبديل لهذه الدوال بحيث أن هذا الكونترول سيعمل بمثابة Items التي سوف نقوم باستخدامها داخل Nested Control
خطوات صناعة الكونترول
مبدئيا نحتاج الي كتابة الكلاس التالية:
1- كتابة الكود المطلوب لبناء Interface
ولكي لا نعقد الأمور علي أنفسنا لنحاول أن نجعله بسيطا بقدر الإمكان وهنا قمت بإضافة بعض Property فقط كما هو واضح في الكود الموجود أدناه
وستلاحظون هنا أنه قد Interface الخاص بنا قد ورث صفاته من IComponent Interface وأيضا أضفت له بعض Properties التي قد نحتاجها لاحقا وكما تلاحظون فإنها جميعا سمات موجودة في اي كونترول تعاملنا معه مثل الألوان والفونت والصور وخلافه
الملحوظة الهامة في هذا Interface هي الصفة Control Property وهي التي سوف تجعلنا نتفادي لاحقا استخدام أسلوب مايكروسوفت القديم في الاستفادة من دوال API
وغالبا هذه الصفة يتم إخفاؤها لاحقا باستخدام Attributes الموجودة في System ComponentModel Class و ذلك عندما نقوم بعمل Implementation لهذا Interface داخل اي كونترول أو Component
كود :
Public Interface IBox
Inherits IComponent
Property Control As Control
Property BoxText As String
Property BoxSize As Size
Property BoxTextColor As Color
Property BoxImage As Image
Property BoxBackColor As Color
Property BoxFont As Font
Property BoxBounds As Rectangle
End Interface ' IBox
وهنا سوف نقوم بعمل بإضافة Interface الي Component حتي تمتلك جميع صفاته
وبالتالي سوف نقوم بعمل Implemetation لهذا Interface داخل Component الخاصة بنا
في واقع الأمر يوجد بعض Properties التي قد نحتاج لاحقا لأن نقوم بعمل Dispose لها لكن وبشكل مؤقت لن أهتم بهذا الأمر وربما نفعله لاحقا
كود :
Public Class BoxComponent
Inherits Component
Implements IBox
#Region " Fields "
Private _backColor As Color
Private _bounds As Rectangle
Private _font As Font
Private _image As Image
Private _size As Size
Private _text As String
Private _textColor As Color
Private _control As Control
#End Region
#Region " Properties "
Public Property BoxBackColor As System.Drawing.Color Implements IBox.BoxBackColor
Get
Return Me._backColor
End Get
Set(ByVal value As System.Drawing.Color)
Me._backColor = value
End Set
End Property
Public Property BoxBounds As System.Drawing.Rectangle Implements IBox.BoxBounds
Get
Return _bounds
End Get
Set(ByVal value As System.Drawing.Rectangle)
_bounds = value
End Set
End Property
Public Property BoxFont As System.Drawing.Font Implements IBox.BoxFont
Get
Return Me._font
End Get
Set(ByVal value As System.Drawing.Font)
Me._font = value
End Set
End Property
Public Property BoxImage As System.Drawing.Image Implements IBox.BoxImage
Get
Return Me._image
End Get
Set(ByVal value As System.Drawing.Image)
Me._image = value
End Set
End Property
Public Property BoxSize As System.Drawing.Size Implements IBox.BoxSize
Get
Return Me._size
End Get
Set(ByVal value As System.Drawing.Size)
Me._size = value
End Set
End Property
Public Property BoxText As String Implements IBox.BoxText
Get
If (Me._text Is Nothing) Then
Dim baseSite As ISite = MyBase.Site
Me._text = baseSite.Name.ToString
End If
Return Me._text
End Get
Set(ByVal value As String)
Me._text = value
End Set
End Property
Public Property BoxTextColor As System.Drawing.Color Implements IBox.BoxTextColor
Get
Return Me._textColor
End Get
Set(ByVal value As System.Drawing.Color)
Me._textColor = value
End Set
End Property
Public Property Control As System.Windows.Forms.Control Implements IBox.Control
Get
Return Me._control
End Get
Set(ByVal value As System.Windows.Forms.Control)
Me._control = value
End Set
End Property
#End Region
End Class
هذا الكلاس سوف يرث الصفات الخاصة بالكلاس CollectionBase بالإضافة لذلك سوف نستفيد من الإمكانيات الموجودة في IList Interface عن طريق عمل Implementation لهذا Interface داخل الكلاس الخاص بنا
من أهم النقاط في هذا الجزء من المقال هو كيفية كتابة الكود الخاص بالدالة GetEnumerator الحقيقة في الفيجوال بيسك الأمر يختلف قليلا عما هو موجود في لغة #C حيث أن لغة #C تمتلك جملة Yield وهي جملة تستخدم لاسترجاع القيم الموجودة في Collection الخاصة بنا من داخل الدالة GetEnumerator اي وببساطة شديدة جدا هي تقوم بدور جملة For...Each الموجودة في الفيجوال بيسك وبما أن جملة Yield غير موجودة في لغة الفيجوال بيسك ّإذن نحن في حاجة إالي أن نكتب كلاسا يلعب نفس الدور الذي تؤدية جملة Yield الموجودة في لغة #C ومن ثم نستخدمه مع الدالة GetEnumerator
وفي هذا الكلاس سوف نقوم بعمل Implementation لكل من IEnumerable و IEnumerator
لذلك اسمحوا لي أن أخرج قليلا عن المقال الأصلي وأوضح كيفية عمل Implementatin لهذان Interfaces
IEnumerable و IEnumerator
ملحوظة واحدة فقط :
من الممكن أن يتم عمل Implementation لكل من IEnumerator أو IEnumerable كل علي حدة ولكن لكي نتفادي كتابة أكثر من كلاس لذلك من الأفضل ان يتم عمل Implementation لهم في كلاس واحد لا غير وبشكل عام هذا أمر متروك لرؤية المبرمج وفهمه الجيد للموضوع. أما وبشكل خاص فالأفضل أن يستخدما معا في كلاس واحد لتوفير الوقت.
مثال :
لنكتب اي كلاس وبأي اسم نراه مناسبا ثم نقوم بعمل Implementation لكل من IEnumerator و IEnumerable
وبالتالي سيكون لدينا الكود بالشكل هذا
كود :
Public Class Vb4ArabEnumerator
Implements IEnumerable
Implements IEnumerator
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
End Function
Public ReadOnly Property Current As Object Implements System.Collections.IEnumerator.Current
Get
End Get
End Property
Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
End Function
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
End Sub
End Class
ثم نضيف Constructor للكلاس ونمرر له المتغير يعبر عن list
كود :
Public Class Vb4ArabEnumerator
Implements IEnumerable
Implements IEnumerator
Private _collection As IList
Private _index As Integer
Public Sub New(ByVal collection As IList)
Me._collection = collection
Me._index = -1
End Sub
' بقية الكود تاتي بعد ذلك
End Class
في الدالة GetEnumerator علينا أن نكتب الكود بحيث نسترجع قيمة جديدة للكلاس Vb4ArabEnumerator ومن ثم نمرر لها المتغير الذي قمنا بتعريفه في بداية الكلاس وهو من متغير من النوع collection
ومن ثم يكون لدينا الكود بالشكل التالي
كود :
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return New Vb4ArabEnumerator(Me._collection)
End Function
الحقيقة من الأفضل استخدام جملة Try......Catch هنا حتي نتجنب اي أخطاء قد تحدث
ومن ثم يكون لدينا الكود بالشكل التالي
كود :
Public ReadOnly Property Current As Object Implements System.Collections.IEnumerator.Current
Get
Return Me._collection.Item(_index)
End Get
End Property
ومن ثم يكون لدينا الكود بالشكل التالي
كود :
Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
Me._index = Me._index + 1
Return (Me._index < Me._collection.Count)
End Function
ومن ثم يكون لدينا الكود بالشكل التالي
كود :
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
Me._index = -1
End Sub
كود :
Public Class Vb4ArabEnumerator
Public Shared Function GetRiverNileEnumerator(ByVal list As IList) As VB4ArabIterator
If (list Is Nothing) Then
Throw New ArgumentNullException("list")
End If
Return New VB4ArabIterator(list)
End Function
Public Class VB4ArabIterator
Implements IEnumerable
Implements IEnumerator
Private _collection As IList
Private _index As Integer
Public Sub New(ByVal collection As IList)
Me._collection = collection
Me._index = -1
End Sub
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return New VB4ArabIterator(Me._collection)
End Function
Public ReadOnly Property Current As Object Implements System.Collections.IEnumerator.Current
Get
Try
Return Me._collection.Item(_index)
Catch ex As Exception
Throw ex
End Try
End Get
End Property
Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
Me._index = Me._index + 1
Return (Me._index < Me._collection.Count)
End Function
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
Me._index = -1
End Sub
End Class
End Class
كود :
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim clrs As New List(Of KnownColor)
clrs.Add(KnownColor.SaddleBrown)
clrs.Add(KnownColor.Red)
clrs.Add(KnownColor.Gold)
For Each clr As KnownColor In Vb4ArabEnumerator.GetRiverNileEnumerator(clrs)
Me.ListBox1.Items.Add(clr)
Next
End Sub
End Class
يتبع........................في المشاركات التالية
تقبلوا تحياتي
أخوكم عمر