السلام عليكم ورحمة الله وبركاته ...............
اذا افترضنا مثلا انه لدينا ثلاثة CompoBox الاول يتيح للمستخدم ان يختار اسم الخط والثاني حجم الخط والثالث لون الخط فاننا قد نقوم بعمل شيئ كالاتي :
CmbFontName.Items.Add("Arial")
CmbFontName.Items.Add("Times new roman")
CmbFontSize.Items.Add(8)
CmbFontSize.Items.Add(10)
CmbFontColor.Items.Add("Blue")
CmbFontColor.Items.Add("Red")لكن ماذا ان كانت البيانات كثيرة اي انه لدينا اكثر من 100 اسم للخطوط واكثر من 100 لون في هذه الحالة تكون اضافة البيانات بالشكل السابق عملية مرهقة !!!! لذا قد يقول قائل لماذا لا نستخدم قواعد البيانات لتخزين البيانات فيها كي يتم استدعائها لاحقا , بالفعل هذا قول ممتاز لكن هذا يتطلب منا معرفة ولو بسيطة في كيفية بناء قواعد البيانات والوصول اليها برمجيا !!
حسنا هل يوجد حل اخر ؟؟؟؟؟؟ نعم يوجد !!!!!! ماذا عن ملفات XML ؟؟؟؟؟؟
الجميل في ملفات XML انها ملفات نصيه يستطيع اي شخص ان يتعامل معها بسهولة !! اذن دعنا نقوم ببناء المستند التالي والذي سيمثل اسماء الخطوط وحجمها ولونها , اذن ماذا تنتظر افتح المفكرة وقم ببناء المستند التالي :
<Editor>
<FontName>Arial</FontName>
<FontName>Times new roman</FontName>
<Size>8</Size>
<Size>10</Size>
<Size>12</Size>
<Size>14</Size>
<Color>sky blue</Color>
<Color>royal blue</Color>
</Editor>
<FontName></FontName> : يمثل اسم الخط
<Size></Size> : يمثل حجم الخط
<Color></Color> : يمثل لون الخط
ملاحظــــــــــــــــــــــــــة : لقد كتبت الموضوع على اساس انه لديك إلمام ببعض اساسيات XML .
حسنا بعدما كتبنا مستند XML السابق سوف نقوم بحفظة في المجلد BIN الخاص بالمشروع تحت اسم Editor.xml مثلا .
الان ياتي دور قراءة البيانات من الملف السابق , وهذا يتلخص بالخطوات التاليه :
1 - انشاء كائن XmlTextReader جديد مع تمرير مسار الملف لطريقة البناء الخاصة بهذا الكائن .
2- الدوران على جميع البنود الموجودة في الكائن XmlTextReader من خلال الطريقة Read والتي تعيد True في حالة وجود Nodes ( عقد ) للقراءة والعكس .
3- اغلاق الملف .
اذن نلاحظ ان اهم نقطة هي النقطة رقم 2 والتي من خلالها سوف يتم اخذ البيانات من ملف ال XML الخاص بنا كي يتم تعبئة ال ComboBox التي لدينا بالقيم المناسبة , الان دعنا نناقش السطر التالي الماخوذ من ملف ال XML الخاص بنا:
<FontName>Arial</FontName>
ماذا تلاحظ ؟؟؟؟؟ الملاحظ هنا انه لدينا عنصر اسمه <FontName> ويحتوي على القيمة Arial !!!!!
هنا ياتي بيت القصيد فنحن نريد ان نقوم باخذ جميع القيم من العناصر ومن ثم تعبئة ال ComboBox بهذه القيم لكن هنا يتبادر سؤالاً الى الذهن الا وهو كيف استطيع التفريق بين العنصر وقيمة العنصر اثناء عملية الدوران وذلك كي اقوم باخذ القيمة فقط ؟؟؟؟؟
هناك خاصيه اسمها NodeType تستخدم في تحديد نوع العقدة التي تقرأ حاليا , فعن طريق هذه الخاصية يمكننا ان نحدد نوع العقدة التي تتم قرائتها الان هل هي عنصر ام قيمة ليس هذا فحسب بل هناك الكثير والكثير حيث يمكننا ان نعرف هل العقدة التي تتم قرائتها عبارة عن خاصية ام تعليق ام هي عبارة عن العقدة الجذر .... الخ ,
هذه الخاصية يمكن ان تاخذ احدى القيم التالية :
Attribute
CDATA
Comment
Document
DocumentFragment
DocumentType
Element
EndElement
EndEntity
Entity
EntityReference
None
Notation
ProcessingInstruction
SignificantWhitespace
Text
Whitespace
XmlDeclaration
الان ما يهمنا هنا هو القيمة Element والذي تدل على ان العقدة التي تقرا حاليا هي عبارة عن عنصر , وكذا القيمة Text والتي تدل على ان العقدة التي تقرا عبارة عن نص .
اذن عن طريق الخاصية NodeType نستطيع تحديد نوع العقدة المقرؤة , والان من اجل سحب قيمة عنصر ما فاننا سوف نستخدم الخاصية المسماة Value .
اذن سيكون سيناريو تعبئة ال ComboBox بالشكل التالي :
عن طريق الخاصية NodeType سوف نقوم بالاستفسار هل العقدة التي تقرا الان عبارة Text ام لا :
Dim XmlRe As New XmlTextReader("Editor.xml")
if XmlRe.NodeType = XmlNodeType.Text then
............
............
............والان بعد ما عرفنا انها Text سوف نقوم بتعبئة ال ComboBox بالقيم المناسبة :
cmbFontName.items.Add XmlRe.value
هنا دعني اسالك اي من ال comboBox سيتم تعبئته فنحن لدينا ثلاثة ComboBox ؟؟؟؟؟؟؟
لهذا الغرض سوف نستعيين بالخاصية Name والتي تعيد لنا اسم العقدة الحالية , حيث اننا سوف نعرف متغير نصي نسميه CurrentNode مثلا ومن ثم نجعله يساوي الخاصية Name في حالة كانت العقدة المقرؤة عبارة عن عنصر Element ومن ثم نختبر قيمته :
Dim CurrentName As String While XmlRe.Read Select Case XmlRe.NodeType Case XmlNodeType.Element CurrentName = XmlRe.Name Case XmlNodeType.Text If CurrentName = "FontName" Then CmbFontName.Items.Add(XmlRe.Value) ElseIf CurrentName= "Size" Then CmbFontSize.Items.Add(XmlRe.Value) ........ ........ End If End Select End While
اذن دعنا نكتب الكود الكامل لهذ المشروع وعلى اعتبار انه لدينا 3 ComboBox تحت الاسماء التالية :
CmbFontName : سيتم تعبئته باسماء الخطوط .
CmbFontSize : سيتم تعبئته بالارقام التي تمثل حجم الخط .
CmbFontColor : سيتم تعبئته باسماء الالوان .
سوف نقوم الان باتباع الخطوات الثلاث المذكورة سابقا الا وهي انشاء مثل مع تمرير اسم الملف لطريقة البناء ومن ثم الدوران خلال العقد ومن ثم اقفال الملف :
Dim XmlRe As New XmlTextReader(IO.Directory.GetCurrentDirectory & "\Editor.xml") ////////////////// انشاء مثل من الصف XmlTextReader Dim CurrentName As String //////////// تعريف المتغير والذي سيتم من خلاله اخذ الخاصيه Name While XmlRe.Read ////// الدوران خلال عقد الملف عن طريق الطريقه Read Select Case XmlRe.NodeType ////////// اختبار نوع العقدة المقرؤة Case XmlNodeType.Element ////////// اذا كانت العقدة عنصر قم باخذ اسم العقدة الحالية CurrentName = XmlRe.Name Case XmlNodeType.Text
//////////// اذا كانت العقدة عبارة عن نص فقم باختبار قيمة المتغير
////// CurrentName فان كانت قيمته تساوي "FontName"
////// فقم بتعبئة ال ComboBox المسمى CmbFontName وهكذا .
If CurrentName = "FontName" Then CmbFontName.Items.Add(XmlRe.Value) ElseIf CurrentName = "Size" Then CmbFontSize.Items.Add(XmlRe.Value) ElseIf CurrentName = "Color" Then CmbFontColor.Items.Add(XmlRe.Value) End If End Select End While XmlRe.Close() //////////// اغلاق الملف .
طبعا الشفرة السابقة يمكنك ان تضعها في زر او في الحدث Load للفورم .
نلاحظ في الفقرة السابقة ان عملية تحديد اسم العقدة تقع على عاتق المبرمج حيث اننا قمنا بتعريف متغير اسمه CurrentName من اجل تحديد اسم العقدة .
الان دعنا نستفيد من تقنية الوراثة , حيث اننا سوف نقوم بوراثة الصف XmlTextReader ومن ثم اضافة خاصية جديدة للقراءة فقط اسمها CurrentElementName , حيث سوف تحتوي هذه الخاصية على اسم العنصر Element الذي تتم قراءة بياناته الان .
السيناريو سيكون كالتالي :
* تعريف صف جديد اسمه MyXml ومن ثم وراثة الصف XmlTextReader .
* تعريف الخاصية CurrentElementName كخاصية للقراءة فقط .
* تعريف طريقة بناء للصف MyXml , حيث سوف نجعل هذه الطريقة تستقبل وسيطا نصيا يحتوي على مسار ملف ال XML المراد قرائته , ومن ثم سوف نقوم بتمرير هذا الوسيط النصي الى طريقة البناء الخاصة بالصف الرئيسي XmlTextReader .
* اعادة قيادة الطريقة Read مرة اخرى , حيث اننا هنا سوف نقوم باختبار نوع العقدة المقرؤة فان كانت Eelement اي عنصر فاننا سوف نقوم باسناد قيمة الخاصية Name والتي نستطيع من خلالها معرفة اسم العنصر الذي يقرا حاليا الى الخاصية CurrentElementName ومن ثم ننفذ الطريقة Read للفئة الام XmlTextReader .
اذن دعنا نقوم بصياغة الكلام السابق برمجيا :
اولا سوف نقوم ببناء الصف الجديد المسمى MyXml ومن ثم وراثة الفئة XmlTextReader .
Class MyXml
Inherits XmlTextReader
End Class
ثانيا تعريف الخاصية CurrentElementName .
Public ReadOnly Property CurrentElementName() As String Get CurrentElementName = P_CurrentElementName End Get End Property
طبعا بما ان الخاصية CurrentElementName للقراءة فقط فاننا سوف نضطر الى تعريف متغير محلي اسمه P_CurrentElementName حيث اننا من خلال هذا المتغير سوف نحصل على قيمة الخاصية Name ومن ثم سوف نقوم باسناد قيمته الى الخاصية CurrentElementName .
Class MyXml Inherits XmlTextReader P_CurrentElementName End Class
ثالثا تعريف طريقة بناء للفئة MyXml .
Sub New(ByVal Path As String) MyBase.New(Path) End Sub
رابعا سوف نقوم بإعادة قيادة الطريقة Read , والتي سوف نستعلم فيها عن نوع العقدة فإن كانت Element فاننا سوف نقوم باسناد الخاصية Name الى المتغير المحلي P_CurrentElementName و ومن ثم سوف نجعل الدالة Read المعاد
قيادتها هنا تعيد القيمة التي ترجعها الدالة Read للفئة الام XmlTextReader .
Public Overrides Function Read() As Boolean Select Case NodeType Case Xml.XmlNodeType.Element
//////// اذا كانت العقدة عبارة عن عنصر Elelemnt فاننا سوف نقوم باسناد الخاصية Name
//////// الى المتغير المحلي P_CurrentElementName
P_CurrentElementName = Name End Select Return MyBase.Read ///// نجعل الدالة تعيد القيمة التي تعيدها الفئة الام XmlTextReader . End Function
وسيصبح الشكل النهائي للفئة بالشكل التالي :
Class MyXml Inherits XmlTextReader //// وراثة الفئة XmlTextReader Private P_CurrentElementName As String
//// التصريح عن المتغير المحلي والذي من خلاله سوف نقوم باسناد قيمة الخاصية Name الى الخاصية CurrentElementName
Public ReadOnly Property CurrentElementName() As String Get CurrentElementName = P_CurrentElementName End Get End Propert
y
//// التصريح عن الخاصية CurrentElementName وجعلها للقراءة فقط
Sub New(ByVal Path As String) MyBase.New(Path) End Sub
///// انشاء طريقة بناء للصف مع جعل طريقة البناء تستقبل وسيطا نصيا يمثل مسار الملف ومن ثم تمريره الى طريقة البناء الخاصة بالفئة الام XmlTextReader .
ملاحظـــــــــة : يمكنك الاستعلام عن الملف ان كان موجودا ام لا عن طريق الفئة File والتي تحتوي على طريق اسمها Exists تقوم بالتحقق من الملف فان كان موجودا فانها تقوم بارجاع True والعكس .
Public Overrides Function Read() As Boolean Select Case NodeType Case Xml.XmlNodeType.Element P_CurrentElementName = Name End Select Return MyBase.Read End Function End Class
/////// اعادة قيادة الطريقة Read مع جعلها في النهاية تعيد قيمة الطريقة Read للفئة الام
الان بعد ان قمنا ببناء الفئة الخاصة بنا سوف نقوم باستخدامها :
Dim XmlRe As New XmlTextReader(IO.Directory.GetCurrentDirectory & "\Editor.xml") While XmlRe.Read Select Case XmlRe.NodeType Case XmlNodeType.Text If XmlRe.CurrentElementName = "FontName" Then CmbFontName.Items.Add(XmlRe.Value) ElseIf XmlRe.CurrentElementName = "Size" Then Me.CmbFontSize.Items.Add(XmlRe.Value) ElseIf XmlRe.CurrentElementName = "Color" Then Me.CmbFontColor.Items.Add(XmlRe.Value) End If End Select End While
نلاحظ من الكود السابق اننا قمنا بالاستعلام عن اسم العنصر الذي تتم قرائته عن طريق الخاصية CurrentElementName مما سهل علينا العمليه .
لكن ماذا إن اردنا تسهيل العملية اكثر واكثر !!!!!!!!!!!!!!!!!!!!!!!!!!
من اجل هذا الغرض دعنا نقوم بتغيير السيناريو السابق ليصبح بالشكل التالي :
سوف نقوم بعمل فئة جديدة اسمها MyXmlWithEvent وهي تشبه الفئة MyXml اللهم اننا وسوف نضيف حدث اسمه TextNode وسنجعله يقدح من داخل الطريقة Read المعاد قيادتها وسوف نقوم بتمرير اسم العقدة كوسيط لهذا الحدث حيث سيقوم مستخدم الفئة بالاعتماد على هذا الوسيط كي يقوم بتحديد اسم العقدة .
حسنا دعنا ناخذ هذا المثال على الواقع :
Public Class MyXmlWithEvent Inherits XmlTextReader Private P_CurrentElementName As String Event TextNode(ByVal ElementName As String)
/////// فقط قمنا هنا بتعريف حدث اسمه TextNode سوف يقوم باستقبال وسيط نصي يمثل اسم العقدة
ومن ثم نذهب الى الطريقة Read المعاد قيادتها ونقوم بقدح الحدث TextNode مع تمرير اسم العقدة اليه :
Public Overrides Function Read() As Boolean Select Case NodeType Case Xml.XmlNodeType.Element P_CurrentElementName = Name Case Xml.XmlNodeType.Text RaiseEvent TextNode(P_CurrentElementName) End Select Return MyBase.Read End Function
/// اولا قمنا باختبار قيمة الخاصية NodeType فاذا كانت تحمل القيمةXmlNodeType.Element فاننا نقوم باسناد
الخاصية Name الى المتغير P_CurrentElementName .
/// ثانيا قمنا باختبار قيمة الخاصية NodeType فاذا كانت تحمل القيمة XmlNodeType.Text فاننا نقوم بقدح الحدث TextNode .
/////// لاحظ ايضا اننا قمنا هنا بتمرير المتغير المحلي P_CurrentElementName والذي تم اسناد قيمة الخاصية Name اليه من سابق كوسيط للحدث TextNode .
الان يمكننا استخدام هذه الفئة الجديدة من خلال السيناريو التالي :
تعريف متغير يشير الى الفئة الجديدة MyXmlWithEvent كالتالي :
Dim XmlReWithEvent As New MyXmlWithEvent(IO.Directory.GetCurrentDirectory & "\FontInformation.xml")
ومن ثم نقوم بقنص الحدث TextNode للمتغير XmlReWithEvent السابق الذكر بواسطة الكلمة المحجوزة AddHandler مع تمرير عنوان الطريقة التي سيتم تنفيذها عندما يقدح هذا الحدث .
AddHandler XmlReWithEvent.TextNode, AddressOf FillComboBox
وهنا سوف نقوم ببناء الطريقة التي سيتم تنفيذها عندما يتم قدح الحدث :
Private Sub FillComboBox(ByVal ElementName As String) Select Case ElementName
///////// لاحظ هنا اننا قمنا بالاستفادة من القيمة المرجعة من الوسيط ElementName لمعرفة اسم العقدة
Case "FontName" CmbFontName.Items.Add(XmlReWithEvent.Value) Case "Size" CmbFontSize.Items.Add(XmlReWithEvent.Value) Case "Color" CmbFontColor.Items.Add(XmlReWithEvent.Value) End Select End Sub
اذن سيصبح الشكل النهائي للكود بهذا الشكل على اعتبار ان هذه الشفرة تنفذ من داخل زر وان هناك متغير معرف في قسم التعريفات باسم XmlReWithEvent :
AddHandler XmlReWithEvent.TextNode, AddressOf FillComboBox
/////// قنص الحدث TextNode مع تمرير عنوان الطريقة FillComboBox
While XmlReWithEvent.Read() End While
////// تنفيذ الطريقة Read
XmlReWithEvent.Close()
////// اغلاق الملف
ونستطيع استخدام الفئة MyXmlWithEvent بطريقة اخرى وهي بتعريف متغير يشير الى الفئة ولكن بإضافة الكلمة المحجوزة WithEvents ليصبح تعريف المتغير بالشكل التالي :
Dim WithEvents XmlReWithEvent As New MyXmlWithEvent(IO.Directory.GetCurrentDirectory & "\FontInformation.xml")
ومن ثم ننفذ الطريقة Read للمتغير XmlReWithEvent من داخل زر ولكن بدون قنص للحدث TextNode كالاتي :
While XmlReWithEvent.Read() End While XmlReWithEvent.Close()
ومن ثم تعديل الطريقة FillComboBox لتصبح بالشكل التالي :
Private Sub FillComboBox(ByVal ElementName As String) Handles XmlReWithEvent.TextNode Select Case ElementName Case "FontName" CmbFontName.Items.Add(XmlReWithEvent.Value) Case "Size" CmbFontSize.Items.Add(XmlReWithEvent.Value) Case "Color" CmbFontColor.Items.Add(XmlReWithEvent.Value) End Select End Sub
اخيرا ارجوا ان اكون قد وفقت في الشرح وانا اسف لانني لم اقم بتغطية بعض المفاهيم كاساسيات Xml وكذا اعادة قيادة الطرق وقنص الاحداث واستخدام الكلمات المحجوزة ك AddHandler و AddressOf و MyBase .
اخوكم Dr.Pain

