در این مقاله به چگونگی ساخت یک فرم ویندوز جهت جمع آوری اطلاعات و قرار دادن کنترلها بر روی آن به صورت پویا از طریق یک فایل XML می‌پردازیم.
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 قبلا تعریف شده است و برابر عدد 300 است.


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'').Inner Text
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 نیز به ترتیب با مقادیر 30 و 19 تعریف شده‌اند. در انتها نیز کنترلهای جدید به کلکسیون کنترلها اضافه شده و مقدار m_Location نیز به عنوان مکان کنترل بعدی برگرداننده می‌شود.