An Example of Dynamic Programming in VB

I've been asked by more than a few people in the community for an example of dynamic programming using Visual Basic. Most people understand the benefits of dynamic programming but are still unsure of the implementation details in VB. But for those readers that are unfamiliar with the term (which by the way seems only recently that dynamic programming became "cool") I want to first talk a little bit about what the heck it means to be "dynamic".

My background is actually rooted in a dynamic programming language, like Visual Basic, but it had much better data handling and integrated query, oh and it was also object oriented (pretty much) back in 1995-ish.  The language was Visual FoxPro. Some of the advantages that VFP and VB both have is that it's really easy to code against objects and data structures that aren't really fully known at design time. The added benefits that VFP has is dynamic execution of code, interactivity, and integrated query against rectangular datasources, called cursors. This enables rich meta-data programming styles and applications where you can change the behavior of a running program without having to recompile. The downside is that VFP is completely dynamic, with no static type checking at all. As most programmers know this can lead you into big trouble if you aren't careful.

Of course, now that VB 9 has integrated query (and a much much fuller implementation than VFP) as well as having static type checking, you should be able to see the reasons why VB is now my language of choice. VB enables static typing where possible and dynamic typing when necessary. It's the only language I know that has both static and dynamic typing capabilities. This is a big win for the types of applications that I typically wrote -- data-based applications and information systems -- where we needed to be able to easily configure and customize the applications without recompiling them and we needed a language that allowed us do this easily with a lot less code to write.

There's still room for more dynamic programming constructs and interactivity with Visual Basic and I think we'll be seeing those improvements with VB 10, but I wanted to show you how you can take advantage of dynamic programming in VB 8 (VS 2005) as well as VB 9 (VS 2008). To demonstrate this I'll create a simple dynamic UI that lays out controls onto a Windows Form by reading an XML document. We'll do this with VB 8 and then I'll write the application in VB 9 with VS 2008 showing some LINQ to XML along the way. 

An Example of Dynamic Programming in VB 8.0, VS 2005

I've created an XML document called questions.xml that contains some information about a survey form I want to dynamically create based on this information. Not only does it contain the questions themselves but it also contains the type of control it should display (as well as which assembly to find the control) and some other additional properties like fore and back colors:

<?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>

    <forecolor>Blue</forecolor>

    <backcolor>Control</backcolor>

  </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>

    <forecolor>Red</forecolor>

    <backcolor>Pink</backcolor>

  </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>

    <forecolor>Cornsilk</forecolor>

    <backcolor>Black</backcolor>

  </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>

    <forecolor>HotPink</forecolor>

    <backcolor>Cyan</backcolor>

  </question>

</questions>

Then I have a Windows Form called Form1. I want to display some fixed content as well as this dynamic content in a scrollable area on the form. To do this I added a Panel, made it scrollable, and then inside the panel placed a TableLayoutControl. I set up this control so that it will always have two columns (for question and answer), but will dynamically grow the rows (based on the number of questions in the XML file). To work with the XML easier in VB 8, I'm going to load it into a DataSet, then we'll iterate the rows and dynamically create the controls:

Dim myData As New DataSet()

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

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

Dim survey As DataTable = myData.Tables(0)

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

For Each row As DataRow In survey.Rows

    Me.AddQuestion(row)

Next

But to put an added twist on things I also want to include a static question object that I created called QuestionInfo. So we want to display the list of statically defined questions along with the dynamic list of questions from the XML file. My QuestionInfo object is a simple class that defines a default property as well (you'll see why this is important in a minute):

Back in our form, I want to create one of these QuestionInfo objects and also add that as a question to our form:

Me.AddQuestion(New QuestionInfo())

So now let's do some dynamic programming. The AddQuestion method is going to call upon a dynamic class we create that will read the information object passed to it (in our case a DataRow or a QuestionInfo object) and return a control configured as described by that object:

Private Sub AddQuestion(ByVal info As Object)

''Dynamic' is a class we created that dynamically creates

' the control and sets properties. Notice that info parameter

' is an object. As long as it has the fields defined that we need,

' this will work as expected. Take a look at Dynamic.vb file

' to see how VB helps with dynamic programming.

Dim tbQuestion As Object = Dynamic.GetQuestion(info)

If TypeOf tbQuestion Is Control Then

With tbQuestion

.Dock = DockStyle.Fill

.TabStop = False

End With

Me.TableLayoutPanel1.Controls.Add(tbQuestion)

Me.AddAnswer() 'Each question has an answer

Else

'We could do something else with this object.

End If

End Sub

Now before I show the code for the Dynamic class I want to mention that the project sets Option Strict ON and Option Explicit ON so this form is all statically typed checked for us. However in the Dynamic class we need to place the code in a separate file with Option Strict Off so that we can enable true dynamic programming. Make sure you only set Option Strict Off for files that contain dynamic code because you definitely want the compiler to help you at design time whenever possible with Option Strict On leading to less bugs in your code.

Option Strict Off

Public Class Dynamic

''' <summary>

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

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

''' </summary>

''' <param name="info">An object with the following properties:

''' assembly

''' control

''' height

''' text

''' forecolor

''' backcolor</param>

''' <returns></returns>

''' <remarks>If these properties are not present a runtime error occurs.

''' In that case a multi-line Textbox is returned with the error message.</remarks>

Shared Function GetQuestion(ByVal info As Object) As Object

Dim c As Object

Try

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

'VB does an automatic conversion at *runtime* when

' setting these properties.

'This is because Option Strict is set to Off.

'Option Strict can be set on a file-by-file basis only

' so make sure you separate your dynamic classes and

' methods (via partial classes) into separate files.

c.Height = info!Height

c.Text = info!Text

c.ForeColor = Color.FromName(info!ForeColor)

c.BackColor = Color.FromName(info!BackColor)

'c.Text = info!Cool 'This will cause a runtime error

If TypeOf c Is TextBox Then

c.ReadOnly = True

c.MultiLine = True

c.ScrollBars = ScrollBars.Vertical

End If

Catch ex As Exception

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

' a runtime error if the info object is missing fields

' or 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

The interesting code here is the use of the bang (!) operator to retrieve the default property sending it the name of the property we are interested in and how VB automatically casts these values for us. Also note how we use structured exception handling to return a TextBox where its Text property is set to the Exception. Exception handling is essential when programming dynamically because you are more likely to get a runtime error than with static programming.

When we run this program we will see our static question as well as all the dynamic questions from the XML document in a scrollable area of our form:

So that's how we do it in VB 8 and Visual Studio 2005.

Same Example in VB 9.0, VS 2008, Using LINQ to XML 

Now we want to upgrade to VB 9 and VS 2008 -- why? -- to take advantage of LINQ to XML and the Intellisense it brings so that we can make our program a little bit safer. It's important to see the balance between static and dynamic programming when you're coding.

When we create a project in VB 9, the setting for Option Strict is Off by default. However, there is a new setting called Option Infer and it is set to On. This means that we do not have to specify the types of our local variables explicitly but the compiler will infer them for us. When the compiler infers the types, they are actually strongly typed. These settings enable better dynamic support.

The first thing we need to do is Import a couple namespaces, System.Linq and System.Xml.Linq. The third namespace is the XML namespace we're going to use. I created an XSD schema from my questions.xml by opening the XML file in VS and from the XML menu selecting "Create Schema". Than I save this schema in the project. Once we do that we can import the namespace:

Imports <xmlns:ns="https://www.w3.org/2001/XMLSchema">

This enables XML Intellisense when we work with the question XElement objects. This helps us immensely when working dynamically. So instead of loading our XML into a DataSet, we're going to create a LINQ to XML query that will give us a collection of XElement objects that represent our questions. We'll then modify our Dynamic class to work with XElement instead of plain Object types. First load the XML document:

Dim survey = XElement.Load(CurDir() & "\questions.xml")

Note that the survey variable here is NOT an object, it's inferred as an XElement because of the right-hand side of the expression. Next we'll select all the question elements no matter how deep they are in the document, using the ... (dot dot dot) syntax. Because we imported the schema, Intellisense shows up when we type the "<" character in the query. Then we iterate over the collection of XElements that are returned from the query and create our question controls:

Dim questions = From q In survey...<question> Select q

'We then add questions for each of the XElements

For Each question In questions

    Me.AddQuestion(question)

Next

Now we need to change the Dynamic class to also import the same namespaces as before including our XML namespace and then modify the class to call the elements on the XElement object to obtain our values:

Option Strict Off

Option Infer On

Imports System.Xml.Linq

Imports System.Linq

Imports <xmlns:ns="https://www.w3.org/2001/XMLSchema">

Public Class Dynamic

    ''' <summary>

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

    ''' by reading elements on the passed in XElement object.

    ''' </summary>

    ''' <param name="info">An XElement with the following elements:

    ''' assembly

    ''' control

    ''' height

    ''' text

    ''' forecolor

    ''' backcolor</param>

    ''' <returns></returns>

    ''' <remarks>If these elements are not present a runtime error occurs.

    ''' In that case a multi-line Textbox is returned with the error message.</remarks>

    Shared Function GetQuestion(ByVal info As XElement) As Object

        Dim c As Object

        Try

            'VB does an automatic conversion at *runtime* when

            ' using these properties, however XML intellisense

            ' is available because we imported the schema above,

            ' which can enables safer dynamic programming.

            'This works because Option Strict is set to Off. In VB9,

            ' the default is to set Option Strict Off, but

            ' Option Infer ON. This gives static typing where possible,

            ' but dynamic typing where necessary. Since this is now

            ' project-wide, we could place this function directly into

            ' the form that uses it.

            c = System.Reflection.Assembly.Load(info.<assembly>.Value).CreateInstance(info.<control>.Value)

            c.Height = info.<height>.Value

            c.Text = info.<text>.Value

            'c.Text = info.<cool>.Value 'This will cause a runtime error

            c.ForeColor = Color.FromName(info.<forecolor>.Value)

            c.BackColor = Color.FromName(info.<backcolor>.Value)

            If TypeOf c Is TextBox Then

                c.ReadOnly = True

                c.MultiLine = True

                c.ScrollBars = ScrollBars.Vertical

            End If

        Catch ex As Exception

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

            ' a runtime error if the XElement is missing fields

            ' or 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

If we run this we'll see our form with all the questions from the XML file displayed dynamically on the form. The LINQ query we used was very simple but keep in mind we could easily select just certain questions or order them how we wanted by adding the appropriate Where or Order By expressions directly into the code. Very simple and elegant.

There is one thing left to do, however. We wanted to also include a question that was created statically from our QuestionInfo object. There are many ways we could do this, but to demonstrate embedded expressions we're going to incorporate our QuestionInfo object into the LINQ to XML query by creating an array of one QuestionInfo object (there could also be multiple objects in the array as well if we wanted), querying over that to select and create an XElement and then Union the results of that query with the query we wrote above.

Dim survey = XElement.Load(CurDir() & "\questions.xml")

Dim qInfo As QuestionInfo() = {New QuestionInfo()}

Dim questions1 = From q In survey...<question> Select q

Dim questions2 = From info In qInfo _

                    Select <question>

                               <assembly><%= info.Assembly %></assembly>

                               <control><%= info.Control %></control>

                               <backcolor><%= info.BackColor %></backcolor>

                               <forecolor><%= info.ForeColor %></forecolor>

                               <text><%= info.Text %></text>

           <height><%= info.Height %></height>

                           </question>

Dim allQuestions = questions1.Union(questions2)

For Each question In allQuestions

    Me.AddQuestion(question)

Next

Now we'll get the same resulting UI as we did before, but now we've made it easier to program dynamically using XML Intellisense to help us stay in check.

I hope this gives you more than a few ideas on how to take advantage of dynamic programming in Visual Basic right now and also in VB 9. I've attached the VS 2005 project as well as the VS 2008 project that should work with Beta 2 once that is released.

Enjoy! 

UPDATE: Also take a look at this updated post that expands on the Dynamic class based on comments from the community.