منتدى فيجوال بيسك لكل العرب | منتدى المبرمجين العرب
مقال - مقدمة لصناعة Nested Control - نسخة قابلة للطباعة

+- منتدى فيجوال بيسك لكل العرب | منتدى المبرمجين العرب (http://vb4arb.com/vb)
+-- قسم : قسم لغة الفيجوال بيسك VB.NET (http://vb4arb.com/vb/forumdisplay.php?fid=182)
+--- قسم : قسم مقالات VB.NET (http://vb4arb.com/vb/forumdisplay.php?fid=184)
+--- الموضوع : مقال - مقدمة لصناعة Nested Control (/showthread.php?tid=5202)



مقال - مقدمة لصناعة 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 في موضوع المقال


تقبلوال تحياتي
أخوكم عمر