Windows Forms: 5 Ways of Interaction Between Forms
While developing Windows Forms application, the following question seems to spring up every so often, “How do I pass information from one form to another?” or its variation “How to I interact with a form from another form?”.
In the days prior to .NET, this was something that was so simple that it was pretty much taken for granted. You could access a forms methods, properties and controls; basically anything on the form that wasn’t private, you could interact with without having to really put any thought (or effort) into it.
Enter .NET. This feature is either “missing” or “removed” depending on whom you speak to. In either case, no matter what side of the fence you are in this debate, there is little doubt that in order to accomplish the same task additional thought and effort has to go into doing so. Since every form (except for the special case of the default form) has to be created as an instanced reference to its underlying class. Once you have an instance, you can then utilize everything not private on that form. However, if you’d like to access another form from within a form, you have to take additional steps to accomplish this. It’s not too difficult, but more work nonetheless and, to make matters a little more difficult, there are several choices for accomplishing this. Here’s an outline of the a few of the ways you could handle this given a parent / child relationship between two forms:
- Pass in the form as part of the child forms constructor.
- Set a property in the child form to the parent form.
- Create a global variable to hold a reference the parent form so it can be utilized from the child form (or any child form).
- Utilize the
Owner
mechanism thatControls
have.
I think you’d agree that any of the above is nowhere as simple as the way VB6 (or earlier) accomplished the same task. But since this is what we have to work with, let’s see some examples of each of these in action. We will create six separate forms. The first form (Form1
) will serve as the parent form and the other five will be the child forms. Each of these will be very similar, contain the same controls and interact with the parent form in the same way. (Disclaimer: This is a rudimentary example just to show concepts and shouldn’t be considered “good” programming practice.) So let’s start with the first child form and then work on the parents form to show the interaction using the constructor model. Create a new Form
(Form3
) and add a text box (TextBox1
) and three buttons (Button1
, Button2
and Button3
). In the code view, we are going to add a new constructor that allows passing in a reference to the parent form (Form1
) and hold this reference in a member variable. Each button will respond to the click event causing an interaction between the child form and the parent.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Public Class Form3
Private m_form As Form1
Public Sub New()
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
Public Sub New(ByVal form As Form1)
Me.New()
m_form = form
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If Not m_form Is Nothing Then
m_form.DoSomething()
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If Not m_form Is Nothing Then
m_form.Label1.Text = TextBox1.Text
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
If Not m_form Is Nothing Then
m_form.Button5.PerformClick()
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
End Class
This form contains a text box and three buttons to cause three separate actions to take place on the parent form. Button1
executes an instance method on the parent form. Button2
will cause the label on the parent to be populated with the text box value. Button3
initiates a click event to fire on the parent forms Button5
. Three relatively simple actions that illustrate a couple of very simplistic ways you could interact with the parent form from the child form. Now let’s modify the parent form to add these three ways of interaction.
On the parent form, add a label (Label1
) and six button controls (Button1
, Button2
, Button3
, Button4
, Button5
and Button6
). Button1
will be ignored until a little later in this article. Button2
will be used to show Form3
. Here’s the code from Form1
up to this point:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Public Class Form1
' Constructor.
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim frm As New Form3(Me)
Me.AddOwnedForm(frm)
frm.Show()
End Sub
' Test button PerformClick action.
Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
If Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None Then
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.Sizable
Else
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None
End If
End Sub
' Test instanced method.
Friend Sub DoSomething()
Me.Text = Now.ToString
End Sub
End Class
Don’t get too caught up in the specifics of what I’m doing with the form, rather focus on the fact that we are going to interact with this form from another; what is being done is not really important in this example. At this point, if you run this code you’ll get the main form displayed to you. Clicking on Button2
will cause another form to be displayed. On the new form, click on Button1
. This causes the caption bar in the main form to show the current date and time. Type something into the text box and click Button2
. This will modify the main forms label with whatever you type in. Click on Button3
and you’ll see the main forms border vanish (and reappear if you keep clicking). Very simple examples, but you can definitely see that the new form is capable of completely interacting with the main form as we expect. Now let’s move on with the next example, setting a property.
Again, create a new form (Form4
), add the same controls as Form3
and add the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Public Class Form4
Private m_form As Form1
Public WriteOnly Property Form() As Form1
Set(ByVal value As Form1)
m_form = value
End Set
End Property
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If Not m_form Is Nothing Then
m_form.DoSomething()
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If Not m_form Is Nothing Then
m_form.Label1.Text = TextBox1.Text
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
If Not m_form Is Nothing Then
m_form.Button5.PerformClick()
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
End Class
Looks very similar to Form3
except that it doesn’t contain a constructor that allows for a reference to the parent form upon instantiation. Rather, this one requires that you set a property at some point to the parent form so that it can be used. At this stage, you might have noticed that all of the click events are checking to make sure that the reference to m_form is testing against not equaling Nothing
and, if it does, throw an error. This is very important as it’s very possible that the consumer of this form (including yourself) might forget to set the reference accordingly. Let’s add a click event to Button3
on the parent form to show Form4
and test it accordingly.
1
2
3
4
5
6
7
8
' Property.
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim frm As New Form4
Me.AddOwnedForm(frm)
frm.Form = Me
frm.Show()
End Sub
Still not very different from the first example except the fact that it takes one additional line of code to prepare before actually showing the form. After testing, you’ll see that it acts very much the same as the first example. Moving along, let’s try out the global variable approach. In addition to having the form, we also need to create a global variable within a Module to assist us. So add a new Module (GlobalForForm5
) and add the following code:
1
2
3
4
5
Public Module GlobalForForm5
Friend g_form As Form1
End Module
The global variable (g_form
) will hold a reference that Form5
will leverage to interact with the parent form. Create a new form (Form5
), add the same controls as Form3
and add the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Public Class Form5
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If Not g_form Is Nothing Then
g_form.DoSomething()
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If Not g_form Is Nothing Then
g_form.Label1.Text = TextBox1.Text
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
If Not g_form Is Nothing Then
g_form.Button5.PerformClick()
Else
Throw New InvalidOperationException("Form1 instance not set.")
End If
End Sub
End Class
Still very similar to the previous two examples. The only difference now is where the reference is being housed (which can be accessed from any part of your application, not just this form). For Button4
on the parent form we’ll use the following event:
1
2
3
4
5
6
7
8
' Global Variable.
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim frm As New Form5
Me.AddOwnedForm(frm)
g_form = Me
frm.Show()
End Sub
Again, not very different from the setting of the property method and, after testing, you’ll see that Form5
behaves very much like the previous two (Form3
and Form4
). The next version leverages some of the behavior built into Windows Forms (more specifically the System.Windows.Forms.Control
class) and is probably the lesser known of the four ways presented here. It’s actually pretty simple but requires that you add the newly created form (child) to the forms (parent) ownership, which causes it to stay on top of the parent form and when the parent is minimized, the child will be minimized as well. So given this limitation, it might not work for in all circumstances and is here for completeness. Again, create a new form (Form6
), add the same controls as Form3
and use the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Public Class Form6
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If Not Me.Owner Is Nothing AndAlso TypeOf Me.Owner Is Form1 Then
CType(Me.Owner, Form1).DoSomething()
Else
Throw New InvalidOperationException("Form1 instance not set or invalid.")
End If
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If Not Me.Owner Is Nothing AndAlso TypeOf Me.Owner Is Form1 Then
CType(Me.Owner, Form1).Label1.Text = TextBox1.Text
Else
Throw New InvalidOperationException("Form1 instance not set or invalid.")
End If
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
If Not Me.Owner Is Nothing AndAlso TypeOf Me.Owner Is Form1 Then
CType(Me.Owner, Form1).Button5.PerformClick()
Else
Throw New InvalidOperationException("Form1 instance not set or invalid.")
End If
End Sub
End Class
Basically, this works by checking to see if the Owner
property of the Control
is set to a reference and checks to make sure that reference is of the correct type. If it is, the Owner
property is cast to the correct type (Form1
) and then we can interact with the parent form accordingly. If the criteria isn’t met, an error is thrown accordingly. (Note that Owner
is not to be confused with Parent
. Parent
is used in relationship to MDI Windows Forms development and is a topic for another discussion.) Use the following event (for Button6
) to show Form6
.
1
2
3
4
5
6
7
' Owner.
Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
Dim frm As New Form6
Me.AddOwnedForm(frm)
frm.Show()
End Sub
Notice with this method we don’t seem to add any type of reference to the parent form. This is done by using Me.AddOwnedForm(frm)
. As you can expect, testing this version will work like the previous three versions.
As you can see, not too difficult, but still not as simple as previous versions of VB. Why did Microsoft change this style of programming and make developing Windows Forms application more difficult? It seems like a small portion of RAD was removed in favor for some idea of having everything overly object oriented. If I create a form, add controls on it and then add some code by double clicking on a button that will take place when I click that button… why do I need to create an instance to that form? And, it seems that this idea isn’t 100%. In fact, it’s possible to have Form1
(which is actually the default) to be automatically (magically, behind the scenes, voodoo, whatever you want to call it) be instantiated and shown upon executing your application. So if the main form is capable of doing this, why shouldn’t child forms have the same capability? As it turns out, VB 2005 will reintroduce this missing component.
(Note: If your one of those that can’t stand the idea of this being reintroduced, you might as well stop reading right here. ;-))
You might have noticed that I skipped over Form2
in the above code. This is because I originally thought about this article in response to some people complaining how “evil” this feature is (referred to as Default Instances
and is associated with Windows Forms and Web Services). While interacting with them, I asked a very simple question in response to them stating that it is really easy to handle this in VB7.0/7.1. I asked them to show me how easy it is and, of course, rather than show me (thus why I created this article), they just responded with “it’s just like using classes”. Although they are similar and, yes, Form is a class; the two aren’t exactly the same. Windows Forms development is somewhat of a special exception in several cases and, in many cases, each of your forms will be single instance throughout your applications lifetime. By this I mean that it will be created, destroyed, hidden and shown throughout the application lifetime; but in most cases, you will only have one of such instances in existence at any given time.
So why should you have to interact with these forms as if your going to create several instances of them? After all, you’ve given the form a name and all the controls a name. Why can’t you just access that form by it’s name and not have to create a new instance of it to do so? This is actually the behavior of the default form in VB7.0/7.1. Now what I don’t understand is that people who are complaining about this feature aren’t considering the fact that the feature already somewhat exists in the current version they are using… go figure. Nonetheless, I didn’t have an opinion until I actually started using it and I have to tell you that I definitely welcome it’s return. It seriously simplifies some difficult Windows Forms coding examples and just seems to be “right” in regards to Rapid Application Development (RAD). So rather than keep standing on my soapbox, let’s look at the code.
(Note: This requires that you have VB8.0 Beta 2 or greater to following along; otherwise you can just take my word for it ;-).)
Add a new form (Form2
), add the same controls as Form3
and use the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Public Class Form2
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Form1.DoSomething()
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Form1.Label1.Text = TextBox1.Text
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Form1.Button5.PerformClick()
End Sub
End Class
Notice that we aren’t having to check to make sure that a variable reference exists and we just refer to the form by its given name (the one we gave it in the designer). OK, so let’s modify Form1
to show this form.
1
2
3
4
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Me.AddOwnedForm(Form2)
Form2.Show()
End Sub
You could show the form with just the 1 line, but I wanted to make it act as an owned form by the parent form (1). Testing this code at this point would show that the form acts very much like the previous four examples, yet takes a lot less code to accomplish the same task. In addition, this flexibility is extended to any form within your application. Sure, this could lead to some convoluted coding practices, but that’s something that is up to the author of the application to decide and not the language designer. Their job is to make our life easier and it’s our job to use the tools appropriately. Default Instances aren’t’ the right tool for every job, but they are (in my opinion) a welcomed addition.
Liked it? Hated it? As always, feel free to leave a comment.