An Example of Dynamic Programming in VB

An Example of Dynamic Programming in VB

Rate This
  • Comments 16

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="http://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="http://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.

Leave a Comment
  • Please add 6 and 1 and type the answer here:
  • Post
  • Hey Beth,

    That's not really what I would call dynamic as the desired cast is actually known at compile time, so it really is a matter of adding a few CTypes.

    Dynamic to me would be more like allowing the user to change control properties and storing them in a database, where the property you want to assign to, and the type is all determined at runtime.  Then it becomes nasty calls to CallByName if you're lucky, or more probably reflection, unless you use VB's late binding (aka dynamic) features ;)

  • Thanks for useful info, I am going to try it.

  • Beth,

     I have to agree with Bill.  These days, if you're going to taut a language as being dynamic, expect people to be a little disappointed if all you're demonstrating is how to create ad hoc objects, even if those ad hoc objects are statically typed.

     Many programmers are working dynamic languages like JavaScript, Python, Ruby these days and have graduated onto lambda functions, closures, and other powerful features that make a dynamic language really powerful.

     Don't get me wrong, I understand above languages are not statically typed and suffer performance hits as a result.  All I'm saying is the expectations for dynamic programming languages are a lot higher today, with one expectation being significantly smaller and expressive code.

  • Hi Bill, Steve,

    I understand what you are saying and VB has a ways to go to enabling more dynamic capabilites but even if the code is only halfway dynamic, it's still dynamic :-). Cutting all the casting code and having the VB do it for you at runtime is still a form of being dynamic -- it does give you significantly smaller code.

    And I didn't mean this example as the only way to do dynamic programming, I just meant it as a single example that shows the runtime realization of the specific control types and the property types themselves. I explored using CallByName so that I could also store the properties in the XML -- and it would have fit the bill perfectly if it would do the casts automatically for you, unfortunately it doesn't.

    I expect to see many more helpful constructs in VB 10 with many more posibilities in the future I just want people to get their head around mixing static and dynamic code in VB and knowing the correct balance.

    As always, thanks for the comments!

  • Hey Beth,

    CallByName in VB6 did do the cast.  So really VB.NET as it stands today is a step backward from dynamic.  

    The example you showed is NOT dynamic.  There is nothing "dynamic" about it, it is all fixed at compile time.  If you can write the same in C# without using reflection, which you can wiht simple casts, then by definition it cannot be dynamic.

    Sorry, but implicit typing is NOT dynamic coding.

  • Hi Bill,

    I guess your definition of dynamic is different than mine :-). I think implicit typing is dynamic -- where I do not have to specify the casts. It's not fixed at compile time -- in the first example I do not know the type of the info object, I just know it has the info I need.

    Cheers,

    -B

  • Hey Beth,

    But it is fixed at compile time.  Let's take the example :

    c.Height = info!Height

    That can also be written as:

    c.Height = info("Height")

    or info.Item("Height")

    So breaking that down we have an itme As Object from a keyed index where the key is a string.  That object may be different but the code is still statically compiled to cast it to Integer in this case (Hieght).  So at compile time, we, and the compiler both know what the type has to be.  So this could be written statically as:

    c.Height = Cint(info!Height)

    And doesn't require Strict Off semantics.  This is not dynamic, it's just impicit casting at compile time.

    Now if c was As Object too, then that would be dynamic, as we wouldn't know at compile time c's type, hence we couldn't know if c.Height even exisited little alone what Type it was.  So there, you couldn't jsut add a cast and statically type the same expression, you'd have to go to great lengths to achieve the same via reflection. Likewise the exampel fo getting the properties and values form a text file or database. These are dynamic because they occur at runtime, not implied at compile time.

  • Hi Bill,

    c *is* an Object so it does require Strict Off. I could have easily generated a runtime error like this:

    c.MyProp = whatever

    The compiler would have let me write that here.

    I'm implicity casting the object c out of the method as a Control, however, because I'm generating controls and I know that ahead of time, I just don't know which ones. I suppose I could return it as an object and check it in the static code on the form. I had done that originally, but changed to avoid the extra explicit cast.

  • LOL. Yes c was As Object, but it served no purpose in being an Object becuase the method was clearly returning it as a Control and all the methods/properties called were based on Control.  So it gains nothing other than avoiding a couple of simple casts and at the same time introduces the possibility of errors like you suggest c.MyProp = ....

    I'm sorry that's not using dynamic for good purposes (and I still argue it's not dynamic becuse everything there can be replaced with casts). So at the end of the day, the code can be calrrified and made mroe robust by addign casts and turning Strict On and still do the exact same thing ;)

  • But it's much much easier this way because I am not gaureenteed to be creating a control at all because I'm reading an XML file. That's the point here.

  • "Much much easier" ??  You're kidding me right ?

    It's only the GetQuestion method that's using Strict Off.  And it is defined as returning a control.

    So define c as Control. (and turn strict On) Now intellisense actually helps you right the code !  And helps you pick up on and eliminate any other mistakes.

    Comments stripped out the code would look like:

       Shared Function GetQuestion(ByVal info As XElement) As Control

           Dim c As Control

           Dim tbx As TextBox

           Try

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

               c.Height = CInt(info.<height>.Value)

               c.Text = info.<text>.Value

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

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

               If TypeOf c Is TextBox Then

                   tbx = CType(c, TextBox)

                   tbx.ReadOnly = True

                   tbx.Multiline = True

                   tbx.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.

               tbx = New TextBox

               tbx.Text = ex.ToString

               tbx.Multiline = True

               tbx.Height = 100

               tbx.ReadOnly = True

               tbx.ScrollBars = ScrollBars.Vertical

               c = tbx

           End Try

           Return c

       End Function

    It's type safe, does the same thign and is much much safer, hence easier because the IDE helps you write it !! ;)

  • Hi Bill,

    I was referring to the first example, it is much easier that way. And BTW, I find doing multiple casts all the time everywhere in my code to be a major PITA. And when I don't know the types how can that be done anyways without late binding. Point taken on returning the control. I will update to return object and then we could expand on our form example to not add objects to the UI if they weren't controls.

    -B

  • BTW, I was wrong about CallByName, it works in VB 8 with simple type conversions. It won't work however, with Color in the above example. I'll post a follow up with some simple properties that are read and set dynamically. Hopefully that will satisfy you a teeny bit more. :-)

  • In a previous post I showed how we could dynamically create a UI based on some XML, however the properties

  • In a previous post I showed how we could dynamically create a UI based on some XML, however the properties

Page 1 of 2 (16 items) 12