Export (0) Print
Expand All

Working with Multiple Forms in Visual Basic .NET: Upgrading to .NET

Visual Studio .NET 2003
 

Duncan Mackenzie
Microsoft Developer Network

June 2002

Summary: Describes how working with multiple forms has changed from previous editions of Microsoft Visual Basic and illustrates several key techniques, including displaying a second form, changing the appearance of another form, and using a form as a dialog. (8 printed pages)

Contents

Introduction
Visual Basic 6.0 vs. Visual Basic .NET
How the Upgrade Wizard Handles This Problem
Interacting with Other Forms in .NET
Conclusion

Introduction

In Microsoft® Visual Basic® 6.0, if you had a second form (Form2) in your project, then displaying it was as easy as Form2.Show. That code is no longer correct in Visual Basic .NET due to some changes in how forms are handled. For programmers moving from an earlier version of Visual Basic to .NET, these changes can make the simple task of displaying a second form seem very difficult. In this article, I will illustrate how Visual Basic .NET forms differ from previous versions and how you can work with multiple forms within the new model.

Visual Basic 6.0 vs. Visual Basic .NET

Forms, in either version of Visual Basic, are essentially the same as any other class; they have properties, methods, and events, and you can create multiple instances of them. So, assuming you have a form in your project called Form2, the Visual Basic 6.0 code shown here creates three instances of that form and displays them all:

Dim myFirstForm As Form2
Dim mySecondForm As Form2
Dim myThirdForm As Form2

Set myFirstForm = New Form2
Set mySecondForm = New Form2
Set myThirdForm = New Form2

myFirstForm.Show
mySecondForm.Show
myThirdForm.Show

Now, other than the use of the keyword Set to assign new Form2 instances to your three variables, this code will also work in Visual Basic .NET, and both languages will display three copies of Form2 if this code is run. In this example, Form2 is behaving as a class, and you have to create an instance of a class before you can use it, but a special feature of Visual Basic prior to .NET allows you to work with Forms without creating an instance. This change in behavior in Visual Basic .NET can cause a great deal of confusion. In Visual Basic 6.0 and earlier versions, a special default instance of each form is automatically created for you, and allows you to use the form's name to access this instance. What this means is that the Visual Basic 6.0 code "Form2.Show" has the effect of showing the "default" instance of Form2, but it doesn't work at all in Visual Basic .NET. In .NET there is no default instance; Form2 refers only to the class that represents your form, and this class cannot be used without creating an instance.

That is the key difference between .NET and previous versions of Visual Basic—you need an instance of a form before you can display it or work with any of its controls or properties—but the second part of the problem is that these special default form instances are global to your entire project in Visual Basic 6.0. Taking these two facts together means that (in Visual Basic 6.0 or earlier) you can refer to any form in your project from anywhere in your code and you will always be referring to the same instance of that form. With the button's Click event on one of your forms you could show Form2 with "Form2.Show," and then set the text of a text box on Form2 from code in a module just like this:

Form2.TextBox1.Text = "Fred"

If you try to do the same type of code in Visual Basic .NET, you will run into the error message Reference to a Non-Shared Member Requires an Object Reference, which means that you tried to call a method or use a property of a class instead of an instance. A quick solution to this problem is to create an instance in every case where you are receiving the error message, turning:

Form2.Show()

into:

Dim myForm2 As New Form2()
myForm2.Show()

This will work in many cases, but if you had code somewhere else in your project that accessed that same default instance of Form2 and you fix it in the same way by turning:

Form2.TextBox1.Text = "Fred"

into:

Dim myForm2 As New Form2()
myForm2.TextBox1.Text = "Fred"

then you will have trouble, because this code has created a new instance of Form2; you are not working with the same instance you created earlier. You won't get any errors (there is nothing wrong with the preceding code), but you won't see any change on the instance of Form2 that you called Show() on earlier.

How the Upgrade Wizard Handles This Problem

If you upgrade a Visual Basic 6.0 project, the Upgrade Wizard adds some special code to each of your forms to provide you with the default instance functionality that you were able to use in earlier versions of Visual Basic. This code, wrapped in a region labeled "Upgrade Support," adds a Shared property that returns an instance of the underlying form:

Private Shared m_vb6FormDefInstance As Form1
Private Shared m_InitializingDefInstance As Boolean
Public Shared Property DefInstance() As Form1
    Get
        If m_vb6FormDefInstance Is Nothing _
            OrElse m_vb6FormDefInstance.IsDisposed Then
            m_InitializingDefInstance = True
            m_vb6FormDefInstance = New Form1()
            m_InitializingDefInstance = False
        End If
        DefInstance = m_vb6FormDefInstance
    End Get
    Set(ByVal Value As Form1)
        m_vb6FormDefInstance = Value
    End Set
End Property

As a shared property, DefInstance is accessible using just the form's name, and the same instance of the form is returned for everyone using this class within a single application. With this code added to your form(s), you can write code that mimics the behavior of the Visual Basic 6.0 examples shown earlier by specifying Form2.DefInstance instead of just Form2.

With this code added, you can use Form2.DefInstance.Show() and then Form2.DefInstance.TextBox1.Text = "Fred" instead of directly referencing Form2. If you are not using the Upgrade Wizard you can achieve similar results by adding the code just shown (and the code from an upgraded form's New procedure, as that is also required) to your own Visual Basic .NET forms. Alternatively, though, if you are not upgrading existing code, you will be better off if you adjust your programming style to handle the lack of default instances for forms. The remainder of this article will focus on showing you how to do that.

Interacting with Other Forms in .NET

If you are coming from earlier versions of Visual Basic it can be difficult to work without default instances of your forms. The following sections illustrate how to handle several specific scenarios and should help you with your own programming tasks.

Keeping a Reference Around

As mentioned earlier, the most important concept to understand about programming with forms is that you need an instance of a form before you can do anything with it, and if you want to use the same instance in multiple places then you will need to pass a reference to that instance around. For many Visual Basic 6.0 programmers, this is a totally new problem; the default instance provided for each form is available to all the code in your project. There are two main ways to handle your form reference(s): either make it globally available or pass it to each form, class, module, or procedure that requires it.

Global data in .NET

Earlier I mentioned that global variables are not available in Visual Basic .NET, and now I'm going to tell you how to make something global. How will you ever trust me after this? Actually, I am not being dishonest in either case; global variables are not allowed in Visual Basic .NET, but you can achieve similar functionality using Shared (called static in C#) class members. A Shared class member, as used by the Visual Basic Upgrade Wizard when it adds the DefInstance property to your forms, is available without creating an instance of a class and, if it is a property, its value is shared across your entire application. You could therefore create a class like this:

Public Class myForms
    Private Shared m_CustomerForm As CustomerForm
    Public Shared Property CustomerForm() As CustomerForm
        Get
            Return m_CustomerForm
        End Get
        Set(ByVal Value As CustomerForm)
            m_CustomerForm = Value
        End Set
    End Property
End Class

When you first create an instance of your form you could store it into this class:

Dim myNewCust As New CustomerForm()
myNewCust.Show()
myForms.CustomerForm = myNewCust

After the CustomerForm property has been populated with an instance of your form, you could then use this class to access that same instance from anywhere in your code:

Module DoingStuffWithForms
    Sub DoExcitingThings()
        myForms.CustomerForm.Text = _
            DateTime.Now().ToLongTimeString
    End Sub
End Module

Storing your form in this manner comes as close to Visual Basic 6.0's Global variables as you are going to get. The next level of variable scope below this level is class (module, class or form, actually) scope, where a variable is declared within a class and is available to any code in that class, and below that is procedure scope, where a variable is local to a single routine.

Passing your form around

As an alternative to making your form global, you could instead keep a reference to the form as a variable in your form or class and then pass that reference to any code that needs access to your form. If you had a form (called Form1 in this example) and you wanted to open a second form (Form2) when a button was clicked, and then do some calculations on this new form in response to another button being clicked, your code could be written within Form1, like this:

Public Class Form1
    Inherits System.Windows.Forms.Form
    Dim myForm2 As Form2

    Private Sub Button1_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles Button1.Click
        myForm2 = New Form2()
        myForm2.Show()
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles Button2.Click
        Calculations.CompoundInterestCalc(myForm2)
    End Sub
End Class

Either method—global references or passing form instances as arguments—will work, but you should pick a method best suited for your particular project. In a situation where only a few procedures will need access to your form after creating it, I would tend towards having a form parameter on those procedures and passing your form reference in as needed. If a form is being used by many procedures throughout your project, a global reference might be in order, but consider restructuring your application so that only a single class or procedure actually needs access to your form. If you were using your form as a place to write out logging information, for instance, you could create a class that exposed the form as a shared member, but also provided a shared WriteToLogWindow method that handled the actual interaction with the form. All of your code would refer to that WriteToLogWindow method instead of accessing the form directly:

Public Class Log
    Private Shared m_LogForm As Form2
    Public Shared Property LogForm() As Form2
        Get
            Return m_LogForm
        End Get
        Set(ByVal Value As Form2)
            m_LogForm = Value
        End Set
    End Property

    Public Shared Sub WriteToLogWindow(ByVal Message As String)
        Dim sb As New _
            StringBuilder(m_LogForm.txtLogInfo.Text)
        sb.Append(Environment.NewLine)
        sb.Append(Message)
        m_LogForm.txtLogInfo.Text = sb.ToString()
    End Sub
End Class

Getting Information into and out of Forms

Thus far in this article I have covered how to obtain and keep track of a form instance, but I haven't discussed how you would get information into and out of form. If you have a form instance, and if your code and your form are from the same project, you can access the controls on a Form directly, but I don't think that is the best idea. Instead of working with the text boxes, buttons, and other controls on your form, I suggest creating properties that are explicitly made Public and allow you to set and retrieve the values you wish to access. Follow me through a quick example if you wish to try out this method of working with a form:

  1. Create a new Windows Application project in Visual Basic .NET.
  2. One form, Form1, will be created automatically. Add another by right-clicking your project in the Solution Explorer and selecting Add Windows Form. Accept the default name of Form2.vb and click OK.
  3. Add two buttons to Form1, leave them with their default names of Button1 and Button2, and reposition them enough so that they are not overlapping.
  4. Add a single text box to Form2, leaving it with the default name of TextBox1.
  5. Now add this code to Form2 (by right-clicking Form2 in the Solution Explorer and selecting View Code) immediately before "End Class."
    Public Property CustomerName() As String
        Get
            Return TextBox1.Text
        End Get
        Set(ByVal Value As String)
            TextBox1.Text = Value
        End Set
    End Property
    

    To do this:

    1. View the code for Form1 and add the following line after "Inherits System.Windows.Forms.Form":
      Dim myForm2 As New Form2()
      
    2. Double-Click Button1 on Form1 to access the button's Click event handler and enter this code:
      myForm2.CustomerName = "Fred"
      myForm2.Show()
      
    3. Double-Click Button2 on Form1 and enter this code:
      MessageBox.Show(myForm2.CustomerName)
      myForm2.CustomerName = "Joe"
      
    4. Run the project (F5), and then click Button1 and Button2 to see the code in action.

Having a CustomerName property might not seem like a big deviation from accessing the text box on Form2 directly, but you gain a few benefits from interacting with your forms in this way. The main benefit is abstraction; you don't have to know anything about the controls on Form2 (there might not even be a text box on it), just set and retrieve the CustomerName property. Having this layer of abstraction allows Form2 to change its implementation without any effect on the rest of your code, making future modifications much easier. In our example, the property-based version wasn't any easier to use, but if you are dealing with a more complex user interface, the code behind your properties could handle all the details while the code to use the form stayed very simple. Of course, one final benefit of this model is less tangible but certainly has value to many developers: Using properties instead of directly accessing the controls is much more elegant and results in code that is easier to understand.

Conclusion

Visual Basic .NET gets rid of the "default instance" used by forms in earlier versions, and that change can cause a great deal of confusion to those trying to learn how to program in .NET. You need an instance of a form before you can access its properties, methods, or any of its controls, and you need that instance stored in such a way that it is available throughout your code. The .NET system for handling forms is more consistent and more powerful than the system in Visual Basic 6.0, but even changes for the better can cause confusion for those trying to make the transition.

For additional information, see the Visual Studio .NET product documentation topics: Upgrading from Visual Basic 6.0 and Forms Changes in Visual Basic .NET.

Show:
© 2014 Microsoft