Posts Windows Forms: 5 Ways of Interaction Between Forms
Post
Cancel

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 that Controls have.
  • </UL>

    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 textbox (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.

    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 textbox 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 textbox 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:

    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 textbox 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:

    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.

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

    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:

    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:

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

    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.

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

    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.

    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 Form2.Show() line, but I wanted to make it act as an owned form by the parent form (Form1).  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.

This post is licensed under CC BY 4.0 by the author.