مقال - مقدمة لصناعة Nested Control - RaggiTech - 05-10-12
كاتب الموضوع : 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
2- كتابة الكود الخاص ببناء Component
وهنا سوف نقوم بعمل بإضافة 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
3- كتابة الكود الخاص ببناء Collection
هذا الكلاس سوف يرث الصفات الخاصة بالكلاس 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
الأن لنملا الفراغات في الكود ولكي نفعل هذا نحتاج لتعريف متغير من النوع IList ويجب أن تكون من النوع التي يمتلك صفة أو Property باسم Item وهذه الصفة متوفرة في بعض الكلاسات في الدوت نت وايضا نحتاج الي تعريف متغير رقمي سيعمل بمثابة index وقيمة هذا المتغير أقل من صفر وهي هنا تساوي القيمة 1-
ثم نضيف 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
في الصفة Current نمرر فيمة المتغير Collection حيث نمرر له المتغير index
الحقيقة من الأفضل استخدام جملة Try......Catch هنا حتي نتجنب اي أخطاء قد تحدث
ومن ثم يكون لدينا الكود بالشكل التالي
كود :
Public ReadOnly Property Current As Object Implements System.Collections.IEnumerator.Current
Get
Return Me._collection.Item(_index)
End Get
End Property
في الدالة MoveNext علينا فقط أن نزيد قيمة المتغير index بمقدار 1+ وبما ان هذه الدالة تسترجع Boolean فكل ما علينا أن نفعله هو أن نتأكد أن قيمة المتغير index أقل من قيمة المتغير collection.Count
ومن ثم يكون لدينا الكود بالشكل التالي
كود :
Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
Me._index = Me._index + 1
Return (Me._index < Me._collection.Count)
End Function
وفي الروتين Reset كلما علينا أن نكتبه هو أن نعيد قيمة المتغير index الي قيمة الأصلية وهي هنا تكون أقل من صفر وتساوي -1
ومن ثم يكون لدينا الكود بالشكل التالي
كود :
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
ولكي نستخدم الكلاس اعلاه افتح اي مشروع وضع في الفورم ListBox ثم اكتب الكود بالشكل التالي
كود :
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
يتبع........................في المشاركات التالية
تقبلوا تحياتي
أخوكم عمر
مقال - مقدمة لصناعة Nested Control - RaggiTech - 05-10-12
تابع..................................
في واقع الأمر عندما يأتي الأمر استخدام IEnumerable و IEnumerator فإن أفضل البدائل لهما كلا من
IEnumerable(Of T) Interface
IEnumerator(Of T) Interface
وفي المثال التالي توضيح لكيفية عمل Implementation لهذان Interfaces
والكلاسات المرفقة بالمثال التالي يمكنكم استخدامها بأي شكل من الأشكال في أكوادكم فهي كلاسات عامة حيث يمكن استخدامها مع مجموعات من Strings و Integers و Colors
مثال :
كود :
''' <summary>
''' Copyright © Omar Amin Ibrahim 2011
''' silverlight1212@yahoo.com
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class RiverNileEnumerator
Public Shared Function GetList(Of T)(ByVal list As IList(Of T)) As RiverNileListEnumerator(Of T)
If (list Is Nothing) Then
Throw New ArgumentNullException("list")
End If
Return New RiverNileListEnumerator(Of T)(list)
End Function
Public Shared Function GetCollection(Of T)(ByVal collection As ICollection(Of T)) As RiverNileCollectionEnumerator(Of T)
If (collection Is Nothing) Then
Throw New ArgumentNullException("collection")
End If
Return New RiverNileCollectionEnumerator(Of T)(collection)
End Function
Public Class RiverNileCollectionEnumerator(Of T)
Implements IEnumerable(Of T)
Implements IEnumerator(Of T)
#Region " Fields "
Private _collectiont As ICollection(Of T)
Private _index As Integer
Private _current As T
#End Region
#Region " Constructor "
Public Sub New(ByVal collection As ICollection(Of T))
MyBase.New()
_collectiont = collection
_index = -1
_current = CType(Nothing, T)
End Sub
#End Region
#Region " Properties "
Public ReadOnly Property Current As T
Get
If _current Is Nothing Then
Throw New InvalidOperationException()
End If
Return _current
End Get
End Property
Private ReadOnly Property System_Collections_Generic_Current As T Implements System.Collections.Generic.IEnumerator(Of T).Current
Get
Return Me.Current
End Get
End Property
Private ReadOnly Property System_Collections_Current As Object Implements System.Collections.IEnumerator.Current
Get
Return Me.Current
End Get
End Property
#End Region
#Region " Methods "
Public Function GetEnumerator() As IEnumerator(Of T)
Return New RiverNileCollectionEnumerator(Of T)(Me._collectiont)
End Function
Public Function MoveNext() As Boolean
_index = _index + 1
If _index = _collectiont.Count Then
Return False
Else
_current = _collectiont(_index)
End If
Return True
End Function
Public Sub Reset()
_index = -1
_current = CType(Nothing, T)
End Sub
Private Function System_Collections_Generic_GetEnumerator() As System.Collections.Generic.IEnumerator(Of T) Implements System.Collections.Generic.IEnumerable(Of T).GetEnumerator
Return Me.GetEnumerator
End Function
Private Function System_Collections_GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return Me.GetEnumerator
End Function
Private Function System_Collections_MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
Return MoveNext()
End Function
Private Sub System_Collections_Reset() Implements System.Collections.IEnumerator.Reset
Me.Reset()
End Sub
Private Sub System_IDisposable_Dispose() Implements IDisposable.Dispose
' do nothing
End Sub
#End Region
End Class
Public Class RiverNileListEnumerator(Of T)
Implements IEnumerable(Of T)
Implements IEnumerator(Of T)
#Region " Fields "
Private _list As IList(Of T)
Private _index As Integer
Private _current As T
#End Region
#Region " Constructor "
Public Sub New(ByVal list As IList(Of T))
MyBase.New()
_list = list
_index = -1
_current = CType(Nothing, T)
End Sub
#End Region
#Region " Properties "
Public ReadOnly Property Current As T Implements System.Collections.Generic.IEnumerator(Of T).Current
Get
If _current Is Nothing Then
Throw New InvalidOperationException()
End If
Return _current
End Get
End Property
Private ReadOnly Property System_Collections_Current As Object Implements System.Collections.IEnumerator.Current
Get
Return Me.Current
End Get
End Property
#End Region
#Region " Methods "
Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of T) Implements System.Collections.Generic.IEnumerable(Of T).GetEnumerator
Return New RiverNileListEnumerator(Of T)(Me._list)
End Function
Private Function System_Collections_GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return Me.GetEnumerator
End Function
Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
_index = _index + 1
If _index = _list.Count Then
Return False
Else
_current = _list(_index)
End If
Return True
End Function
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
_index = -1
_current = CType(Nothing, T)
End Sub
Private Sub System_IDisposable_Dispose() Implements IDisposable.Dispose
' do nothing
End Sub
#End Region
End Class
End Class ' RiverNileEnumerator
ولإستخدام الكلاس الموجود أعلاه
افتح مشروع واضف الي الفورم 2 ليست بوكس واكتب الكود بالشكل التالي
كود :
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim ColorList As ICollection(Of KnownColor) = New List(Of KnownColor) From {KnownColor.Aqua, KnownColor.Red, KnownColor.Black}
For Each clr As KnownColor In RiverNileEnumerator.GetCollection(ColorList)
ListBox1.Items.Add(clr)
Next
Dim StringList As IList(Of String) = New List(Of String) From {"Omar", "Amin", "Ibrahim"}
For Each Str As String In RiverNileEnumerator.GetList(StringList)
ListBox2.Items.Add(Str)
Next
End Sub
End Class
طبعا لا أريد أن أطيل عليكم في الموضوع الخاص بكل من IEnumerable و IEnumerator وبشكل عام من لديه اي استفسار في هذا الأمر يمكنه أن يسأله بالتأكيد
وفي المشاركات التالية سنناقش كيفية الاستفادة من هذان Interfaces في موضوع المقال
تقبلوال تحياتي
أخوكم عمر
|