XML یکی از مهمترین ارکان ویژوال استودیو دات نت است که امروزه در صنعت فناوری اطلاعات کاربرد بسیاری دارد. در این مقاله به چگونگی ساخت یک فرم جمع آوری اطلاعات و قرار دادن کنترلها بر روی آن به صورت پویا می‌پردازیم. اما قرار دادن کنترلها بر روی فرم به صورت کلاسیک و قدیمی صورت نمی‌گیرد بلکه این کار از روی یک فایل XML انجام می‌شود. در واقع این فایل XML برای برنامه مشخص می‌سازد که سوالات و جوابهای ما در قالب چه کنترلهایی بر روی فرم به نمایش درآیند. با این کار برای بروز رسانی برنامه کافیست سوالات و جوابهای خود را در فایل XML بروز کنیم بدون آنکه در برنامه خود تغییری ایجاد کنیم.

در مرحله اول یک شئ از فرم frmSurveyForm میسازیم. این فرم شامل دو دکمه OK و Cancel است که از پیش طراحی شده و سایر کنترلها را نیز بطور پویا ایجاد میکنیم. همچنین یک کلکسیون از کنترلها به نام surveyControls میسازیم که همان کلکسیون کنترلهایی است که قرار است بر روی فرم قرار گیرد. کلکسیونها، آرایه‌هایی هستند که اعضای آن میتوانند از چند نوع داده تشکیل شوند. همانطور که میدانید در ویژوال بیسیک دات نت تمام کنترلها درون یک کلکسیون قرار داده میشوند و به هر کدام از آنها یک شماره اختصاص داده میشود که میتوان با فراخوانی اندیس هر عضو به آن کنترل دسترسی پیدا کرد. توجه داشته باشید که آخرین کنترل اضافه شده به فرم اولین اندیس را دارد. این کدها را میتوان در رویداد یک دکمه قرار داد:

Dim survey As New frmSurveyForm()
Dim surveyControls As Control.ControlCollection = survey.SurveyFormControls

m_Location = New Point(10, 10)

Dim xr As New Xml.XmlDocument()
xr.Load("..\Questions.xml")

xr یک سند XML است که پس از ساختن آن یک سند XML را از به آن نسبت میدهیم. در این مثال فایل XML مورد نظر را در همان پوشه پروژه قرار داده‌ایم. این فایل به این مقاله ضمیمه شده است. سپس یک متغیر رشته‌ای به نام myTag میسازیم که این متغیر شامل مقدار خصوصیت name از تگ survey میباشد. استفاده از تگهای سند XML فواید زیادی را دربر دارد. با این روش برنامه قابلیت گسترش بیشتری را خواهد داشت و میتوان با اضافه کردن یا کاهش سوالات/ جوابها برنامه را به روز کرد.

Dim myTag As String = xr.SelectSingleNode("//survey").Attributes("name").Value

از سند XML خصوصیت displayName را خوانده و آنرا به عنوان Caption فرم ست میکنیم.

survey.SurveyTitle = xr.SelectSingleNode("//survey").Attributes("displayName").Value

خصوصیت SurveyTitle قبلا درون فرم frmSurveyFrom بصورت زیر تعریف شده است:

Public Property SurveyTitle() As String
  Get
    Return m_Title
  End Get

  Set(ByVal Value As String)
    m_Title = Value
    Me.Text = m_Title
  End Set
End Property

یک لیست گره (XMLNodeList) که شامل هر یک از سوالات میاشد ساخته و آنرا پر میکنیم:

Dim nodeList As Xml.XmlNodeList
nodeList = xr.GetElementsByTagName("question")

یک گره XML موقت میسازیم که هنگام بازیابی اطلاعات در مورد گره‌ها از لیست گره‌هایی که ساختیم به کار میرود. پس از آن شروع به خواندن یک به یک گره‌ها میکنیم و با توجه به خصوصیت type از هر گره کنترلی مناسب با آن میسازیم. این کار را با فراخوانی توابع نوشته شده انجام میدهیم. برای مثال اگر نوع آن برابر dropdown باشد با فراخوانی تابع Survey_AddComboBox یک ComboBox روی فرم ساخته میشود.

Dim myNode As XmlNode

For Each myNode In nodeList
  If Not myNode.Attributes Is Nothing Then

    Select Case myNode.Attributes("type").Value
      Case "dropdown"
      m_Location = Survey_AddComboBox(myNode, surveyControls, _
                           m_Location, myTag)

      Case "multilist"
      m_Location = Survey_AddListBox(myNode, surveyControls, _
                           m_Location, myTag, True)

      Case "text"
      m_Location = Survey_AddTextBox(myNode, surveyControls, _
                           m_Location, myTag)

      Case "radio"
      m_Location = Survey_AddRadioButtons(myNode, surveyControls, _
                           m_Location, myTag)
    End Select
  End If
Next

عرض و ارتفاع فرم نظرخواهی را با توجه به تعداد کنترلهای قرار داده شده و ابعاد آنها تنظیم میکنیم. این کار با استفاده از یک متغیر سطح ماژول به نام m_Location انجام می شود.

Private m_Location As New Point(10, 10)

مقدار خروجی توابع فوق همان m_Locaton است که با این کار پس از ساختن هر کنترل مقدار این متغیر که نشان دهنده مکان کنترل بعدیست تنظیم میشود. همچنین مقداری فضا را برای دو دکمه OK و Cancel خالی میگذاریم. ثابت CONTROL_WIDTH قبلا تعریف شده است و برابر عدد ۳۰۰ است.

survey.Width = m_Location.X + CONTROL_WIDTH + 30
survey.Height = m_Location.Y + 75

در آخر نیز فرم را نمایش می دهیم:

survey.ShowDialog()

همانطور که مشاهده کردید با این روش یک فرم جمع آوری اطلاعات ساختیم. در قسمت دوم این مقاله به بررسی چگونگی ساخت کنترلها به صورت پویا می پردازیم. این کار را با توضیح توابع Survey_AddComboBox و Survey_AddListBox و Survey_AddTextBox و Survey_AddRadioButtons انجام می دهیم. همچنین خصوصیاتی در قسمت کد فرم frmSurveyFrom تعریف شده است که میتوانید با استفاده از فایل ضمیمه این مقاله آنها را مشاهده کنید.

تابع Survey_AddComboBox:
این تابع یکComboBox به کلکسیون کنترلها اضافه میکند. همچنین یک Label جهت نمایش سوال مربوطه نیز ساخته میشود.

Private Function Survey_AddComboBox(ByVal inNode As XmlNode, _
        ByVal inControls As Control.ControlCollection, _
        ByVal location As Point, ByVal tag As String) As Point

  Dim myCombo As New ComboBox()
  myCombo.Text = ""
  myCombo.Name = inNode.Attributes("name").Value
  myCombo.Tag = tag
  myCombo.Width = CONTROL_WIDTH

  Dim myNode As XmlNode
  For Each myNode In inNode.SelectNodes("responses/response")
    myCombo.Items.Add(myNode.InnerText)

    If Not myNode.Attributes("default") Is Nothing Then
      If myNode.Attributes("default").Value = "true" Then
        myCombo.Text = myNode.InnerText
      End If
    End If
  Next

  Dim myLabel As New Label()
  myLabel.Name = myCombo.Name & "Label"
  myLabel.Text = inNode.SelectSingleNode("text").InnerText
  myLabel.Width = CONTROL_WIDTH

  myLabel.Location = location
  inControls.Add(myLabel)
  location.Y += myLabel.Height

  myCombo.Location = location
  inControls.Add(myCombo)
  location.Y += myCombo.Height + 10

  Return location
End Function

در این تابع ابتدا یک ComboBox جدید ساخته و خصوصیات آنرا تنظیم میکنیم. پس از آن یک گره XML موقت جهت بازیابی اطلاعات از گره response می‌سازیم و سپس با خواندن گره‌ها اطلاعات آنها را به لیست اضافه می‌کنیم. اگر خصوصیت Default یک گره مقدار true داشته باشد، مقدار آن گره به عنوان مقدار Text در ComboBox نوشته میشود. پس از آن یک Label جدید ساخته و آنرا به کلکسیون کنترلها اضافه میکنیم. در آخر ComboBox جدید هم به کلکسیون کنترلها اضافه می‌شود. همچنین مقدار m_Location به عنوان مکان کنترل بعدی برگرداننده می‌شود.

تابع Survey_AddListBox:
این تابع یک لیست باکس (ListBox) به کلکسیون کنترلها اضافه میکند. همچنین یک Label جهت نمایش سوال مربوطه نیز ساخته میشود.

Private Function Survey_AddListBox(ByVal inNode As XmlNode, _
        ByVal inControls As Control.ControlCollection, _
        ByVal location As Point, ByVal tag As String, _
        ByVal isMultiSelect As Boolean) As Point

  Dim myList As New ListBox()
  myList.Text = ""
  myList.Name = inNode.Attributes("name").Value
  myList.Tag = tag
  myList.Width = CONTROL_WIDTH

  If isMultiSelect Then
    myList.SelectionMode = SelectionMode.MultiSimple
  Else
    myList.SelectionMode = SelectionMode.One
  End If

  Dim myNode As XmlNode
  For Each myNode In inNode.SelectNodes("responses/response")
    myList.Items.Add(myNode.InnerText)

    If Not myNode.Attributes("default") Is Nothing Then
      If myNode.Attributes("default").Value = "true" Then
        myList.Text = myNode.InnerText
      End If
    End If
  Next

  Dim myLabel As New Label()
  myLabel.Name = myList.Name & "Label"
  myLabel.Text = inNode.SelectSingleNode("text").InnerText
  myLabel.Width = CONTROL_WIDTH

  myLabel.Location = location
  inControls.Add(myLabel)
  location.Y += myLabel.Height

  myList.Location = location
  inControls.Add(myList)
  location.Y += myList.Height + 10

  Return location
End Function

در این تابع ابتدا یک ListBox جدید ساخته و خصوصیات آن از جمله خصوصیت MultiSelect را با توجه به پارامتر ارسال شده تنظیم میکنیم. پس از آن یک گره XML موقت جهت بازیابی اطلاعات از گره response می‌سازیم و سپس با خواندن گره‌ها اطلاعات آنها را به لیست اضافه می‌کنیم. اگر خصوصیت Default یک گره مقدار true داشته باشد، مقدار آن گره به عنوان مقدار Text در ListBox نوشته میشود. در آخر نیز ListBox جدید به کلکسیون کنترلها اضافه شده و مقدار m_Location به عنوان مکان کنترل بعدی برگرداننده می‌شود.

تابع Survey_AddRadioButtons:
این تابع یک GroupBox به کلکسیون کنترلهای قبلی اضافه میکند که شامل دکمه های رادیویی و یک Lable برای نمایش سوالات است.

Private Function Survey_AddRadioButtons(ByVal inNode As XmlNode, _
        ByVal inControls As Control.ControlCollection, _
        ByVal location As Point, ByVal tag As String) As Point

  Dim myGroupBox As New GroupBox()
  myGroupBox.Text = ""
  myGroupBox.Name = inNode.Attributes("name").Value
  myGroupBox.Tag = tag
  myGroupBox.Width = CONTROL_WIDTH + 20

  Dim myRadio As RadioButton
  Dim myRadioPoint As New Point(5, 10)
  Dim myNode As XmlNode

  For Each myNode In inNode.SelectNodes("responses/response")
    myRadio = New RadioButton()
    myRadio.Text = myNode.InnerText
    myRadio.Location = myRadioPoint
    myRadioPoint.Y += myRadio.Height

    If Not myNode.Attributes("default") Is Nothing Then
      If myNode.Attributes("default").Value = "true" Then
        myRadio.Checked = True
      End If
    End If

    myGroupBox.Controls.Add(myRadio)
  Next

  myGroupBox.Height = myRadioPoint.Y + 5

  Dim myLabel As New Label()
  myLabel.Name = myGroupBox.Name & "Label"
  myLabel.Text = inNode.SelectSingleNode("text").InnerText
  myLabel.Width = CONTROL_WIDTH

  myLabel.Location = location
  inControls.Add(myLabel)
  location.Y += myLabel.Height - 5

  myGroupBox.Location = location
  inControls.Add(myGroupBox)
  location.Y += myGroupBox.Height + 10

  Return location
End Function

در این تابع ابتدا یک GroupBox جدید ساخته و خصوصیات آنرا تنظیم میکنیم. پس از آن یک دکمه رادیویی میسازیم. بعد از آن با استفاده از یک حلقه جوابها را در دکمه های رادیویی می نویسیم و مقدار پیش فرض روی دکمه رادیویی مربوطه با توجه به خصوصیت default گره تنظیم می‌شود. ارتفاع GroupBox نیز با توجه به محتوی دکمه های رادیویی تنظیم می شود. در آخر نیز کنترلهای جدید به کلکسیون کنترلها اضافه شده و مقدار m_Location به عنوان مکان کنترل بعدی برگرداننده می‌شود.

تابع Survey_AddTextBox:
این تابع یک TextBox و یک Lable برای نمایش سوالات به کلکسیون کنترلهای قبلی اضافه میکند.

Private Function Survey_AddTextBox(ByVal inNode As XmlNode, _
        ByVal inControls As Control.ControlCollection, _
        ByVal location As Point, ByVal tag As String _
        ) As Point

  Dim myText As New TextBox()
  myText.Tag = tag
  myText.Width = CONTROL_WIDTH

  If Not inNode.SelectSingleNode("defaultResponse") Is Nothing Then
    myText.Text = inNode.SelectSingleNode("defaultResponse").InnerText
  End If

  If Not inNode.Attributes("name") Is Nothing Then
    myText.Name = inNode.Attributes("name").Value
  End If

  If Not inNode.SelectSingleNode("maxCharacters") Is Nothing Then
    myText.MaxLength = Integer.Parse(inNode.SelectSingleNode_
    ("maxCharacters").InnerText)
  End If

  If myText.MaxLength > 0 Then
    Dim numLines As Integer = (myText.MaxLength \ CHARS_PER_LINE) + 1

    If numLines = 1 Then
      myText.Multiline = False
    Else
      If numLines >= 4 Then
        myText.Multiline = True
        myText.Height = 4 * HEIGHT_PER_LINE
        myText.ScrollBars = ScrollBars.Vertical
      Else
        myText.Multiline = True
        myText.Height = numLines * HEIGHT_PER_LINE
        myText.ScrollBars = ScrollBars.None
      End If
    End If
  End If

  Dim myLabel As New Label()
  myLabel.Name = myText.Name & "Label"
  myLabel.Width = CONTROL_WIDTH

  If Not inNode.SelectSingleNode("text") Is Nothing Then
    myLabel.Text = inNode.SelectSingleNode("text").InnerText
  End If

  myLabel.Location = location
  inControls.Add(myLabel)
  location.Y += myLabel.Height

  myText.Location = location
  inControls.Add(myText)
  location.Y += myText.Height + 10

  Return location
End Function

در این تابع ابتدا یک جعبه متن جدید ساخته و برخی از خصوصیات آنرا با توجه به فایل XML تنظیم میکنیم. پس از آن تعداد خطوط مجاز، بزرگی جعبه متن و اینکه آیا ScrollBar نیاز است یا خیر محاسبه میشود. دو ثابت CHARS_PER_LINE و HEIGHT_PER_LINE نیز به ترتیب با مقادیر ۳۰ و ۱۹ تعریف شده‌اند. در انتها نیز کنترلهای جدید به کلکسیون کنترلها اضافه شده و مقدار m_Location نیز به عنوان مکان کنترل بعدی برگرداننده می‌شود.