Sunday, July 26, 2009

VB.NET Default Form Instances

What is a default form instance?

VB.NET is a fully object oriented language so, as such, basically everything you use is an object. Each object is an instance of its type, e.g. a String object is an instance of the String class. Creating an instance of a type is also known as instantiating that type. When dealing with classes, with the exception of the String class, the only way to instatntiate a type is to invoke a constructor, which is done with the New keyword:

Dim someVariable As SomeType = New SomeType

That code declares a variable whose type is SomeType, creates an instance of the SomeType type and then assigns the object to the variable. VB.NET also supports the following abbreviated syntax, which is functionally equivalent:

Dim someVariable As New SomeType

In your code, you may use various means to get new objects, e.g. some classes may have a Create method that returns a new instance, but all such methods will be using constructors internally. Forms are a special case in VB.NET as of VB 2005, which introduced the concept of default instances.

Every time you add a new form to your project you are creating a new class that inherits the System.Windows.Forms.Form class. Like other classes, forms have constructors and you can create an instance by invoking a constructor, but you can also use the class’s default instance. The default instance is an object of that type that the VB application framework creates and manages for you. For instance, conventionally you would do this to show a form:

Dim myForm As New SomeForm
 
myForm.Show()

If you use the default instance then you don’t need to invoke a constructor explicitly. You simply access the default instance directly via the My.Forms object:

My.Forms.SomeForm.Show()

or just using the class name:

SomeForm.Show()

It should be noted that, once compiled, this code will still cause a constructor to be invoked in order to create the default instance. The significance of this will be explained later.

One of the main goals of VB has always been to provide power while make programming as easy as possible for as many people as possible. The introduction of default form instances is in furtherance of that goal. Many people new to OOP use objects in VB.NET but they don’t really comprehend them properly. As such, they can have a great deal of difficulty dealing with forms as objects and making forms interact with each other. This is also true of some more-experienced VB6 developers who have made the move to VB.NET. VB.NET default instances behave like VB6 default instances so they feel familiar to VB6 developers making the move to VB.NET.

When should you use default instances?

If you’re going to use default instances at all then you should use them pretty much all the time. I’ll qualify that statement later but, for the moment, let’s say that you should use them all the time or not at all.

One important point to note is that, if you leave the application framework enabled when you create a Windows Forms Application project, your startup form is the default instance of its type. If you don’t know what the application framework is then you can safely assume that it’s enabled and not worry about it.

The fact that your startup form is a default instance is quite useful. For instance, let’s say that you are displaying multiple records in a DataGridView in the main form and you open a new form for the user to edit one of those records. Once the editing is done, you want to update the main form with the new data before closing the dialogue. How do you do it? This is the sort of thing that causes great confusion to those new to OOP and VB.NET. In .NET OO terms, the dialogue needs a reference to the main form in order to update it. Providing that reference is actually not too difficult but the mechanism is not immediately obvious to many. With default instances it’s easy. The dialogue form can access the default instance of the main form’s type using the class name, so it can access the main form, e.g.

Private Sub okButton_Click(ByVal sender As Object, _
                           ByVal e As EventArgs) Handles okButton.Click
    With Form1.DataGridView1.CurrentRow.Cells
        .Item(0).Value = Me.TextBox1.Text
        .Item(1).Value = Me.TextBox2.Text
    End With
 
    Me.Close()
End Sub

In this case, when the user clicks the OK button, the dialogue will directly access the grid on the main form and update the cells of the current row.

Another example of where default instances are helpful is in a multiple-document interface (MDI) application. The child forms in an MDI application are not inherently aware of each other. Let’s say that your MDI application consists of a parent form and two child forms. Let’s say that making a change on one child form needs to cause a change on the other child form. If the children don’t inherently know about each other, how can this be done? The answer is, again, that the first child form simply references the default instance of the second child form’s type via the class name. This will allow changes to be made to the second child form, assuming that it is the default instance of its type.

That last sentence touches on an important point and a source of confusion for some. There’s no point updating the default instance of a form class if you haven’t actually displayed the default instance of that class in the first place. This is one of the reasons I say that, if you’re going to use default instances at all, you should use them all the time. For instance, if you create a form and display it like this:

Dim myForm As New SomeForm
 
myForm.Show()

then you can’t then refer to the default instance of the SomeForm class and expect the form on your screen to be affected. You’ve created one instance and displayed that, then the system creates the default instance. They are two different objects so making changes to one will not affect the other. If you’re going to make changes by referring to the default instance then you need to have displayed the default instance in the first place:

SomeForm.Show()

Now, default instances don’t hold much appeal for the experienced VB.NET developer. The situations I’ve used as examples up to now should never occur in a well-designed application. For instance, the first example involves a dialogue updating a DataGridView on the main form. In a well-designed application the dialogue shouldn’t have to even know that the main form exists. The dialogue should simply make the new data available via public properties and close. The main form would then retrieve the data itself and update its own UI. In the second example, where a change on one MDI child form causes a change in another child form, the child forms should, again, not have to have knowledge of each other. In a well-designed application the first child would raise an event that can be handled by the parent form, which can then update the other child. The parent form created both the children so it has to have knowledge of both, so it should have no problem accessing both.

There is one situation though, where using the default instance can provide a small benefit to the experienced developer. Because there is always one and only one default instance, they can be useful where you want your form to exhibit singleton-like behaviour. For instance, let’s say that you have a menu item that is supposed to display a form. If the form isn’t already open you want it to be displayed but, if it is already open, you want the existing form to receive focus. Normally you would have to retain a reference to the form when you open it and then, when the menu items is clicked, you need to check whether there is an existing form and, if there is, whether it has already been disposed:

Private toolWindow As SomeForm
 
Private Sub OpenToolWindowToolStripMenuItem_Click(ByVal sender As Object, _
                                                  ByVal e As EventArgs) _
Handles OpenToolWindowToolStripMenuItem.Click
    If Me.toolWindow Is Nothing OrElse _
       Me.toolWindow.IsDisposed Then
        'The tool window has not been opened or it has been opened and closed.
        Me.toolWindow = New SomeForm
        Me.toolWindow.Show()
    Else
        'A Tool window is currently open.
        Me.toolWindow.Activate()
    End If
End Sub

Using the default instance, that code simplifies to this:

Private Sub OpenToolWindowToolStripMenuItem_Click(ByVal sender As Object, _
                                                  ByVal e As EventArgs) _
Handles OpenToolWindowToolStripMenuItem.Click
    'Make sure the tool window is displayed.
    SomeForm.Show()
 
    'Make sure the tool window has focus.
    SomeForm.Activate()
End Sub

That does neaten the code a bit, although it’s not a huge improvement, especially if you need to handle events of the form and/or access its methods and/or properties. Personally, I’d just stick with the first style of code but it’s good to know your options.

When can’t you use default instances?

I said earlier that, if you’re going to use default instances at all, you should use them all the time. That is generally true but there are certain situations where you can’t use them. For less experienced developers particularly, these situations will be very much in the minority and may not even be encountered at all in many projects, but it’s important to be aware of them.

1. You need to display multiple instances of the same form class simultaneously.

An example of this would be an MDI application where multiple documents are opened at the same time, all using the same form class. I said earlier that there is always one and only one default instance so, obviously, that presents a problem if you need multiple instances open simultaneously. If you’re going to have to create one or more instances explicitly then you should create them all explicitly. Consistency is a good thing and, if all instances are equivalent, they should be treated in exactly the same way.

2. You need to pass data to the form when you create it.

When you access the default instance for the first time the system must create it and to do that it must invoke a constructor. Specifically, it invokes the constructor that has no parameters. If you want to create a form by invoking a constructor with parameters then you must do so explicitly. It’s also worth noting that if you remove the parameterless constructor and leave only one or more constructors with parameters then your form class will have no default instance. Trying to access it in that case will cause a compilation error.

3. You need to access a form from a secondary thread.

In order to access a form from a secondary thread you generally need to test its InvokeRequired property and then call its Invoke method. I said earlier that there is only ever one default instance of a form class. That’s not strictly true. In fact, default instances are thread-specific, so there is only ever one default instance per thread. As such, if you test the InvokeRequired property of the default instance you will always be accessing the default instance for the current thread, not the one that was displayed on the main thread.

While experienced .NET developers will probably never feel the need to use default instances, those new to OOP or VB.NET may find them useful. They do have their pitfalls though so, like anything, it’s good to understand what you’re dealing with.

14 comments:

Chris128 said...

Another useful article, thanks :) One thing that I think might make it more complete (and perhaps more useful for beginners) is a description of how you can store and retrieve references to instances of forms if you are not using the default instances.
I know I certainly used to just use the default instances because I didnt really know how to keep track of multiple instances. Obviously there are a couple of ways to do it so it would be good to see your recommendations and comments.

Anonymous said...
This comment has been removed by a blog administrator.
Tanner said...

This was helpful. Thank you.

A couple of questions though,

1. Is the default instance's constructor a "hidden" constructor or just the one with no parameters in my class? The default instance would call the same constructor that would be called if I did "blah = new Foo()" correct?

2. As Chris128 mentioned what is the "preferred" method for storing/retrieving non-default instances? Currently I use modules to hold a reference.

jmcilhinney said...

Tanner,

Your first question is addressed in the post:

"When you access the default instance for the first time the system must create it and to do that it must invoke a constructor. Specifically, it invokes the constructor that has no parameters."

All forms, and in fact all classes, have a default constructor with no parameters. It's not hidden and it's provided for you. That default constructor is removed if you add any constructors of your own, with or without parameters.

With regards to your second question, in a well written app there should be no need to store references in common locations like modules. In the vast majority of cases, if a form needs a reference to another form it will already have it because it will have created the other form in the first place. If it seems difficult to get a reference to a form then it's most likely that you shouldn't be trying to access that form anyway, but more likely raising an event that the other form can handle or the like.

Anonymous said...

Nice dispatch and this fill someone in on helped me alot in my college assignement. Thanks you for your information.

Anonymous said...
This comment has been removed by a blog administrator.
pepoluan said...

Very clear explanation! Many thanks!

BTW, I visited the page thanks to the link you left at Dan Mabbut (About.Com) article "To Instantiate or not To Instantiate"

I have ran smack-drab into default instance limitations, but have been hesitant to actually do explicit instantiation due to fears that Forms just *might* be some special Object. And your article put my fears to rest :)

Now excuse me while I rewrite my codes to use explicit instantiation ;)

Unknown said...

Great article mate! Not only the explanation about the default instances but about the most common situations we should use/not use them. Thanks a lot!

Ramon Araujo said...

Awesome! You explain the essence of using Default Instances, and also in which situations we should and should not use them. Thanks a lot!

Anonymous said...

You can still make add parameter to default instance by create Public Sub New (para1, para2, etc.
)

jmcilhinney said...

@Anonymous, no you cannot. You don't add parameters to the constructor of the default instance. You don't add parameters to the constructor of any instance. You can add a constructor with parameters to a class and then invoke that constructor to create an instance of that class. In the case of default form instances though, they are only created by the system and the system will only invoke a parameterless constructor to do so. If there is no parameterless constructor on a form class then you can't use a default instance of that class.

JSmith said...

Thank you, my problem is solved now

Anonymous said...

Hello,

I followed your link from StackOverflow. Thank you for the wonderful article. I have some questions though:

1. I have a class that isn't a form, but it still seems to create default instances. Is this something that is supposed to possible? Visual Studio actually used to display the icon for this class as a Windows Form until I put before the Class definition.

2. Is there any way to instruct VB to *never* create a default instance for a class? I did it several places by accident, and it results in a bunch of runtime bugs that were hard to track down.

Thanks again for the help and for the great article.

jmcilhinney said...

To answer those questions:

1. If that type has a default instance then it's a form, at least as far as the system is concerned. If you're saying that you created a form and then tried to make it not a form then it appears to have failed. You should add a new class to your project and then copy the code across that you want to keep.

2. I'm not aware of a way to prevent default instances being available. There's really no reason to accidentally use one though. When you refer to a default instance the IDE highlights the name just as it does the type name elsewhere, so you can see if you're using one. You should know that you use a constructor each time you create a new object and, regardless of default instances, you shouldn't use type names for variables.