Making our Code More Dynamic

In a previous post I showed how we could dynamically create a UI based on some XML, however the properties we wanted to set were known because we wrote the XML. I want to follow up with an extension of that sample that can dynamically set the properties on our objects that we're creating by reading the property names from the XML as well.

We need to rewrite the Dynamic class so that it can get and set the properties dynamically. We'll turn to VB's CallByName function to help us (this is similar to the SetProperty() VFP method). Currently this function only works with simple types, however, so we cannot dynamically set our colors with this technique, though it can still be powerful in many scenarios. Let's take a look:

My new XML document looks like the following: 

<?xml version="1.0" encoding="utf-8" ?>

<questions>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.TextBox</control>

    <text>This is the first survey question.</text>

    <height>35</height>

    <readonly>true</readonly>

    <tabstop>false</tabstop>

    <multiline>true</multiline>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.TextBox</control>

    <text>This is the second survey question.</text>

    <height>100</height>

    <readonly>true</readonly>

    <tabstop>false</tabstop>

    <multiline>true</multiline>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

    <control>System.Windows.Forms.Label</control>

    <text>This is the third survey question.</text>

    <height>80</height>

    <tabstop>false</tabstop>

  </question>

  <question>

    <assembly>System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</assembly>

  <control>System.Windows.Forms.Button</control>

    <text>This is the fourth survey question.</text>

    <height>30</height>

    <tabstop>false</tabstop>

  </question>

</questions>

Now we just read the list of properties from the document. We can do this easily in VB 8 using a DataSet again:

'Read the xml into a dataset for easier processing of the elements

Dim myData As New DataSet()

myData.ReadXmlSchema(CurDir() & "\questions.xsd")

myData.ReadXml(CurDir() & "\questions.xml")

Dim survey As DataTable = myData.Tables(0)

'Read the properties from the XML

Dim props As New List(Of String)

For Each dc As DataColumn In survey.Columns

  props.Add(dc.ColumnName)

Next

'Now add all the questions defined in the questions.xml file

For Each row As DataRow In survey.Rows

Me.AddQuestion(row, props)

Next

Now we can pass this list of properties into the Dynamic function along with the info object which can either be a DataRow in this case or our static QuestionInfo object we created previously. Note that I didn't change the QuestionInfo class definition, this code will ignore any properties that are not found on the info object or ones that cannot be set on the return object. This makes the code more dynamic because now we don't necessarily know the type of the object we're creating or the properties that are being set on it:

Option Strict Off

Public Class Dynamic

    ''' <summary>

    ''' Dynamically creats an object and sets properties on it

    ''' by reading properties on the passed in object.

    ''' </summary>

    ''' <param name="info">An object with the property values</param>

    ''' <param name="properties">The list of properties to set from the info

    ''' object onto the created object</param>

    ''' <returns></returns>

    Shared Function GetQuestion(ByVal info As Object, ByVal properties As List(Of String)) As Object

        Dim c As Object

        Dim propValue As Object

        Try

            c = System.Reflection.Assembly.Load(info!Assembly).CreateInstance(info!Control)

'VB does an automatic conversion at runtime

            For Each prop As String In properties

                Try

                   'Late bound call - we don't know the type or the value 

                    propValue = info(prop)

                Catch ex As Exception

                    propValue = Nothing

                End Try

                If propValue IsNot Nothing AndAlso propValue IsNot System.DBNull.Value Then

                   Try

                        CallByName(c, prop, CallType.Set, propValue)

                    Catch ex As Exception

                   'if we can't set a property on the object, just ignore

                    End Try

                End If

            Next

        Catch ex As Exception

            'Try/Catch is required here, as this code will cause

            ' a runtime error if the the type cannot be created.

            c = New TextBox

            c.Text = ex.ToString

            c.MultiLine = True

            c.Height = 100

            c.ReadOnly = True

            c.ScrollBars = ScrollBars.Vertical

        End Try

        Return c

    End Function

End Class

 

I've attached the new sample to this post. No one can deny that VB still has a long way to go to enable better support here, but we can see that the beginings of dynamic programming in Visual Basic today.

Enjoy!

DynamicUI.zip