• (2 Pages)
  • +
  • 1
  • 2

Passing Data Between Forms Rate Topic: ***** 1 Votes

#1 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 2890
  • View blog
  • Posts: 9,597
  • Joined: 12-December 12

Posted 27 October 2013 - 09:32 AM

Introduction

This tutorial will guide you through a few different ways to pass information between WinForms.




This tutorial will NOT show you how to directly read, or change, the content of a TextBox (or any other Control) on another Form. This breaks the principle of Separation of Concerns, or encapsulation. Each form should, as far as possible, be able to function independently. They should not be reliant upon, or even know too much about, any other form.




  • Using Modal (Dialog) Forms
  • Using a Public Property of a Form
  • Passing Data Using a Constructor
  • Subscribing to FormClosing and FormClosed Events
  • Raising, and Listening for, an Event

  • Sharing Data - INotifyPropertyChanged


This is an introduction to this subject and is suitable for some straight-forward form-interaction. I do not discuss Data Binding or make any attempt to synchronise forms. I also use VB.NET's Simplified Event Model.

Posted Image

Using Modal (Dialog) Forms

This is the simplest approach. Rather than using Show() to open another form, using ShowDialog() will open the form modally. The form will behave like a MessageBox. It will wait for you to respond to, and close, this form before returning to your main form. We can obtain a value from this modal-form, safe in the knowledge that the form has been closed (and so the value we have obtained will not be changed).

Posted Image

Do stuff..
Create a WinForm application and rename the initial (main) form to frmMain by right-clicking Form1.vb in the Solution Explorer. Press Yes in response to the message-prompt: this will change references to this name throughout your Project. Add a Button to this form and name it btnBoldModal. It is the top-left button in the first screenshot above. Add a TextBox named txtFirstName.

Create a second form - you can choose the Project menu, Add Windows Form... Rename it from the Solution Explorer to frmBoldModal. Add a Label (I called it lblConfirmation) with the Text 'Make FirstName Bold?'.

Add three Buttons to this form: btnYes, btnNo, btnCancel. Change the DialogResult property of these buttons to Yes, No and Cancel respectively.
..relax

The DialogResult property means that when we show this form modally (ShowDialog) pressing, for example, the Yes button will return the value Yes to our main form. (There are other values for this property that you can select from the list that appeared.)

Add the following code to the Click event of the Button on the main form.

    Private Sub btnBoldModal_Click(sender As Object, e As EventArgs) Handles btnBoldModal.Click
        Dim dialog As frmBoldModal

        dialog = New frmBoldModal()
        Dim result As DialogResult = dialog.ShowDialog(Me)
        '(Me) states that this, the main form, is the owner of the dialog
        If result = Windows.Forms.DialogResult.Yes Then
            txtFirstName.Font = New Font(txtFirstName.Font, FontStyle.Bold)
        ElseIf result = Windows.Forms.DialogResult.No Then
            txtFirstName.Font = New Font(txtFirstName.Font, FontStyle.Regular)
        End If
    End Sub


You can now run this application. Pressing Yes or No on the second form will toggle bold-text for the first-name. Cancel doesn't change anything, it just closes the form.

There is no code in the second form. In particular, we did not need to add Me.Close() to any of the three buttons. The fact that the form is opened modally, and these buttons have their DialogResult set, means that clicking any of the buttons will automatically close the form, and return the corresponding value - the Button's DialogResult.

Notice that dialog was declared as a local variable. Because the form is opened modally, it will not persist any longer than it takes you to click one of the buttons, so there is no point in making dialog a form-level reference.




Posted Image

For this second example we want the form to behave more like an InputBox, returning some text that we enter into the form. We will achieve this using a Public Property of the form. (Definitely NOT by attempting to read the TextBox.Text directly! cf separation of concerns)

Do stuff..
To the main form add a Button (btnNoteModal), TextBox (txtLastName - which will be used later) and Label (lblNote). This is the second row in the first screenshot.

Create another form named frmNoteModal. Add a TextBox (txtNote) and two Buttons. Set their DialogResult properties to Yes and No respectively. I've set their Text properties to Yes and No as well, but you can use whatever Text you want (within reason).
..relax

Add the following code to the form frmNoteModal. Hint: Type just 'Property' and press Tab twice (quickly) and VS will generate the code-stub, or snippet, for you. Pressing Tab will then navigate you to fill in the specifics for the Property.

Public Class frmNoteModal
    Public Property Note As String
        Get
            Return txtNote.Text
        End Get
        Set(value As String)

        End Set
    End Property
End Class


This Public Property will be available to us from the main form. It returns the Text of the TextBox without us having to refer to the TextBox directly, or even to worry about where this value came from. This is the dream!

The Set is empty. We don't need it. However, I've left it in-place as otherwise we would need to change the Property declaration to be ReadOnly. If you wish to do this then just delete the Set construct; VS will mark the word Note and you can point, and click at, this word and VS will offer to insert the ReadOnly specifier for you.

In the main form add the following code to btnNoteModal.

    Private Sub btnNoteModal_Click(sender As Object, e As EventArgs) Handles btnNoteModal.Click
        Dim dialog As frmNoteModal

        dialog = New frmNoteModal()
        Dim result As DialogResult = dialog.ShowDialog(Me)
        If result = Windows.Forms.DialogResult.Yes Then
            lblNote.Text = dialog.Note
        End If
    End Sub


dialog.Note is the key thing here. This is reading the (public) property of the dialog-form. Notice that we are able to read this, and other, properties of the form even though it is closed. The form is kept in scope during the current procedure (as long as we hold a reference to it).

Using a Public Property of a Form

The purpose of the following is to pass an object to another form. The object (a Person-instance) is passed using a Public Property of the second (child) form. The Person is shared between the two forms. That is, both forms hold a reference to the same Person-instance, and both can modify the state of this instance. This is a common approach but, obviously, has implications. This is discussed further in the (optional) section Sharing Data - INotifyPropertyChanged.

It doesn't have to be a complex object. You could just supply a variable, or variables, using Public Properties. See the previous example.

Add the following Person Class to your Project.

Spoiler

(This Class demonstrates three different ways to define Properties.)

Create a new form named frmChildProperty. Add a Button to the main form named btnShowChildProperty.

Modify the code of the main form to include the following.

Spoiler

I haven't added any Controls to frmChildProperty, just the following code.

Spoiler

Reminder: The purpose of this example is simply to pass an object to a form. The MessageBox confirms that the object was successfully received.

Passing Data Using a Constructor

Forms have a default constructor which accepts no arguments. We can simply create another constructor (New) that does take an argument, or arguments. The new form can use and store these values, perhaps as public properties.

Do stuff..
Add a new Button to the main form (btnConstructor). Create a new Form (frmConstructor) with a Button (btnConfirm).
..relax

New code for the main form:
    Private frmCon As frmConstructor

    Private Sub btnConstructor_Click(sender As Object, e As EventArgs) Handles btnConstructor.Click
        frmCon = New frmConstructor("Willy", "Wombat")
        frmCon.Show()
    End Sub


Code for frmConstructor:
Spoiler

Now that we have defined a constructor that takes arguments we can no longer use the default (nullary) constructor New(). If you wish to be able to do this as well then implement this constructor in your code.

This approach takes care of passing data to the other form. You can combine this with the other approaches discussed in this tutorial. You can read a public property of the form, perhaps when the form is closed, or you can create, and subscribe to, a custom event of the form.

Subscribing to FormClosing and FormClosed Events

As we shall soon see, subscribing to - listening for - the FormClosing or FormClosed events of another form is quite straight-forward. We need to include the WithEvents keyword when declaring the form-variable, which must be declared at the Class or Module level. This confirms that we wish to receive events raised by the object using the Handles keyword.

The main difference between FormClosing and FormClosed is that it is possible to Cancel the Closing event, preventing the form from closing. However, this is not relevant as we will not, and should not, attempt to prevent the closing of another form. The form itself (and the user) should remain responsible for this decision.

Subscribing to these events is fine, but how useful is it? If the second form is working completely independently of the first, then we may not need to know when it closes. One circumstance might be that when we open the second form we also tile both forms. When the second form closes we can detect this and perhaps maximize the first form.

More often, we need to be aware of whether the second form achieved its objective. A simple way to do this is to refer to a Boolean, Public Property of the second form. Compare this to a dialog-form, which automatically provides this information, simply by us setting the DialogResult property of specific buttons.

Do stuff..
Add a button to the main form named btnClosure. Create another form named frmClosure. Add two Buttons to this form named btnYes and btnNo.
..relax

Modify the main form's code to include the following. Hint: After typing:

AddHandler frmCls.FormClosing, AddressOf ItIsClosing

VS will underline 'ItIsClosing'. Point, and click at, this word and the editor offers to create the procedure-stub for you.

Spoiler

Many handlers can be added to the same event (cf. multi-cast delegates) so each AddHandler should have a corresponding RemoveHandler. However, providing you take care, and dispose of the form (by setting the form-reference to Nothing) when it is no longer needed then this needn't be too much of a concern.

Code for frmClosure:
Spoiler

Raising, and Listening for, an Event

Posted Image

This form can remain open and, at any time, selecting a colour from the ListBox will change the background-colour of the main form. This is achieved by the second form raising a (custom) event that the main form is listening for / has subscribed to using Handles. This uses the simplified event model, using the Event keyword:

Public Event ColourChanged(ByVal chosenColour As Colour)

declares the Event object (in the second form)
RaiseEvent ColourChanged(lstColours.SelectedItem)

raises this event..
Private Sub OnColourChange(ByVal chosenColour As Colour) Handles frmColour.ColourChanged

..in the main form listens for, and responds to, this event.

Add the following code to the beginning of frmMain.vb, before Public Class frmMain. This will help us to convert the word "Red" selected in the ListBox to the colour-value Color.Red, etc..

Public Structure Colour
    Dim ColourName As String
    Dim ColourValue As Color

    Public Overrides Function ToString() As String
        Return ColourName
    End Function
End Structure


Add a Button named btnShowChildColour to the main form and create a form named frmChildColour. Add a ListBox control to this form named lstColours.

New code for the main form:
Spoiler

Code for frmChildColour:
Spoiler

There are more formal ways to create custom events, using Delegates, etc.. Understanding Delegates and Events is important, particularly if you want to understand how VB.NET really works! Look through the other tutorials here @DIC.

Concluding

The approaches outlined above, and combinations of them, will cover most circumstances. Beyond this lie the topics of Delegates, Advanced Event Model, Data Binding (including synchronising forms), Validation, etc..

Note: Another approach using an Interface is outlined here at Geek Goddess. It is in C# though. This is an interesting approach but is uncommon.




Sharing Data - INotifyPropertyChanged

This is an optional, additional topic.

I want to explore sharing data between form-instances in a little more detail. In particular, using the INotifyPropertyChanged Interface. This is dipping into the area of Data Binding and I want to stress that I am not recommending the following approach. It is merely an introduction to this subject, but may give you an insight into what needs to happen, and the challenges that are faced, when attempting to synchronise data with Forms, their Controls and Properties.

Add a new Button to the main form named btnChildShare and a Label lblSalary, then a new form named frmChildCtor.

Posted Image

The four TextBoxes are named txtFirstName, txtLastName, txtAddress and txtSalary, the Buttons btnChange and btnClose. I am not really using some of these controls for this example, which is why they are disabled.

Change the Person.vb file to the following.

Spoiler

This implements the INotifyPropertyChanged interface. This requires the PropertyChanged Event. The onpropertychanged procedure is just a helper method that raises the PropertyChanged event. It is not essential but gives us the opportunity to perform some last checks, and to tailor the arguments supplied to the event, before eventually firing the event.

Changes to the main form:
Spoiler

Key differences are:
    Private WithEvents thePerson As Person

This is the shared-object and we want to subscribe to any events of this object.
    frmPerson = New frmChildCtor(thePerson)
    AddHandler thePerson.PropertyChanged, AddressOf ChangedSalary
    frmPerson.Show(Me)


We are subscribing to the PropertyChanged event of the (shared) person instance.
Private Sub ChangedSalary(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs)
    If e.PropertyName = "Salary" Then
        lblSalary.Text = String.Format("{0:C}", thePerson.Salary)
    End If
End Sub


This is the procedure that will be called when the PropertyChanged event happens. If the property that was changed is the person's salary then this will update the Label that shows this salary.

Code for the frmChildCtor:
Spoiler

The (shared) Person is passed via the constructor. The only other significant detail here is this line:
ThePerson.Salary = decSalary


This updates the salary for the person, which triggers the PropertyChanged event in the Person Class, which causes the ChangedSalary code to run in the other form.

This post has been edited by andrewsw: 27 October 2013 - 12:27 PM


Is This A Good Question/Topic? 3
  • +

Replies To: Passing Data Between Forms

#2 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 2890
  • View blog
  • Posts: 9,597
  • Joined: 12-December 12

Posted 28 October 2013 - 01:20 PM

I will add the following as a note as I am happy with my tutorial content as it stands.

An alternative way to provide the onpropertychanged helper method in the last (additional) example is:

    ' This method is called by the Set accessor of each property. 
    ' The CallerMemberName attribute that is applied to the optional propertyName 
    ' parameter causes the property name of the caller to be substituted as an argument. 
    Private Sub onpropertychanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

See the example at INotifyPropertyChanged :MSDN. This requires this import:

Imports System.Runtime.CompilerServices

in the Person Class.

This means that we can call onpropertychanged without supplying the property-name, as it will be supplied by the CallerMemberName() attribute.

This is interesting, but not essential.
Was This Post Helpful? 1
  • +
  • -

#3 thava  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 179
  • View blog
  • Posts: 1,600
  • Joined: 17-April 07

Posted 29 October 2013 - 05:21 AM

View Postandrewsw, on 28 October 2013 - 01:20 PM, said:

I will add the following as a note as I am happy with my tutorial content as it stands.

An alternative way to provide the onpropertychanged helper method in the last (additional) example is:

    ' This method is called by the Set accessor of each property. 
    ' The CallerMemberName attribute that is applied to the optional propertyName 
    ' parameter causes the property name of the caller to be substituted as an argument. 
    Private Sub onpropertychanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

See the example at INotifyPropertyChanged :MSDN. This requires this import:

Imports System.Runtime.CompilerServices

in the Person Class.

This means that we can call onpropertychanged without supplying the property-name, as it will be supplied by the CallerMemberName() attribute.

This is interesting, but not essential.

that was a good one, and it's only applicable for Framework 4.5, please also add the if statement and it's purpose in the property setter part

This post has been edited by thava: 29 October 2013 - 05:36 AM

Was This Post Helpful? 0
  • +
  • -

#4 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 2890
  • View blog
  • Posts: 9,597
  • Joined: 12-December 12

Posted 29 October 2013 - 05:56 AM

Hello. thava and thank you for your support :)

The linked MS page doesn't suggest that CallerMemberName() is only available from .NET 4.5. Do you have a link that confirms this please?

Also, I'm not sure which if-statement and setter you are referring to? The only relevant setter is for Salary in the Person Class:

Set(ByVal value As Decimal)
    _Salary = value
    Me.onpropertychanged("Salary")
End Set

Perhaps you mean the amendment to this:
Set(ByVal value As Decimal)
    If Not (value = _Salary) Then 
        Me._Salary = value
        onpropertychanged()
    End If 
End Set 

Yes, this is a good addition, only triggering the event if the value has, actually, changed.

I might just keep this note here, as part of this comment, rather than amending the tutorial.

Thanks again.
Was This Post Helpful? 0
  • +
  • -

#5 thava  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 179
  • View blog
  • Posts: 1,600
  • Joined: 17-April 07

Posted 29 October 2013 - 12:18 PM

hi thanks for your response
just see this link how to implemant this funtionality in 4,3,2

This post has been edited by thava: 29 October 2013 - 12:20 PM

Was This Post Helpful? 1
  • +
  • -

#6 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 2890
  • View blog
  • Posts: 9,597
  • Joined: 12-December 12

Posted 29 October 2013 - 12:27 PM

Thank you thava.

That link is for C# but it shouldn't be too tricky to find, or make, a VB.NET equivalent.
Was This Post Helpful? 0
  • +
  • -

#7 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4314
  • View blog
  • Posts: 7,480
  • Joined: 08-June 10

Posted 03 November 2013 - 11:04 AM

Speaking of C#...Here's a C# tutorial (shameless plug: I wrote it) that covers similar material, for anyone interested:

http://www.dreaminco...ny-other-forms/

Great tutorial, andrewsw!
Was This Post Helpful? 1
  • +
  • -

#8 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 2890
  • View blog
  • Posts: 9,597
  • Joined: 12-December 12

Posted 03 November 2013 - 11:38 AM

@Curtis Thank you and, yes, that's a great tutorial as well ;). I read it before creating mine. In fact, it encouraged me to write mine as there wasn't an equivalent VB.NET tutorial.
Was This Post Helpful? 0
  • +
  • -

#9 bergeronjc  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 19
  • Joined: 29-October 13

Posted 07 November 2013 - 12:05 PM

I'm missing something here. On frmChildProperty the following code gives me an error that says "FirstName is not a member of String" and says the same for LastName.

MessageBox.Show("Person Received:" & vbCrLf & ThePerson.FirstName & " " & ThePerson.LastName)


In the Person Class is says
Public Property FirstName() As String
and
Property LastName As String


Why does it tell me that FirstName is not a member of String? I'm a little lost with that one. I like to think I'm getting good at debugging but this error has thrown me for a loop.
Was This Post Helpful? 0
  • +
  • -

#10 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4314
  • View blog
  • Posts: 7,480
  • Joined: 08-June 10

Posted 07 November 2013 - 12:15 PM

My guess is that you've declared ThePerson as a String variable. Please show us where you create that variable.
Was This Post Helpful? 1
  • +
  • -

#11 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 2890
  • View blog
  • Posts: 9,597
  • Joined: 12-December 12

Posted 07 November 2013 - 12:21 PM

Hello. I just created a new project, added a second form and a Class file, then copied and pasted the code sections (from that topic) for:

Person Class, frmMain and frmChildProperty

After adding two textboxes and a button I was able to run the project successfully.

Just check it again carefully ;). Curtis has probably nailed it ;)

This post has been edited by andrewsw: 07 November 2013 - 12:23 PM

Was This Post Helpful? 0
  • +
  • -

#12 bergeronjc  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 19
  • Joined: 29-October 13

Posted 07 November 2013 - 12:29 PM

It was what Curtis said.
    Private _Person As Person
    Public Property ThePerson() As Person
        Get
            Return _Person
        End Get
        Set(ByVal value As Person)
            _Person = value
        End Set
    End Property


'As Person' was 'As String' but right when I changed it the errors went away...VB.Net is my first attempt to learn a language and while it is super fun and I love it, it certainly can be maddening. Thank God for this website.

Now with the below code it is telling me "Value of type 'String' cannot be converted to 'System.Windows.Forms.Textbox"

    Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        thePerson = New Person With {.FirstName = "Bob", .LastName = "The Builder"}
        txtFirstName = thePerson.FirstName
        txtLastName = thePerson.LastName
    End Sub


What does the Textbox use if not 'String'?
Was This Post Helpful? 0
  • +
  • -

#13 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 2890
  • View blog
  • Posts: 9,597
  • Joined: 12-December 12

Posted 07 November 2013 - 12:39 PM

The error message is pretty clear. You are attempting to assign to the TextBoxes, not their Text property:

        txtFirstName.Text = thePerson.FirstName
        txtLastName.Text = thePerson.LastName

This post has been edited by andrewsw: 07 November 2013 - 12:39 PM

Was This Post Helpful? 0
  • +
  • -

#14 bergeronjc  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 19
  • Joined: 29-October 13

Posted 07 November 2013 - 12:56 PM

Wow, I can't believe I missed that. I think it may be break time.

Thanks for quick reply.

View Postandrewsw, on 07 November 2013 - 12:39 PM, said:

The error message is pretty clear. You are attempting to assign to the TextBoxes, not their Text property:

        txtFirstName.Text = thePerson.FirstName
        txtLastName.Text = thePerson.LastName

Was This Post Helpful? 0
  • +
  • -

#15 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5316
  • View blog
  • Posts: 11,373
  • Joined: 02-June 10

Posted 09 November 2013 - 10:11 AM

First let me say how excited I am to see to banging out these tutorials. Love it!

This is the only one that I have a little concern over.
Partly because it is dealing with forms, which are just plain outdated in today's .NET development world. Sure there are plenty of programs still in them - but WPF if rapidly taking over. The idea that new developers should be spending 10,000 hours on old tech just rubs me the wrong way because it doesn't prepare them for getting a job 2 years from now.

And partly because the subject belays the biggest problem: Passing data between forms. That shouldn't happen except in your first case of simple dialogs like SaveFileDialog. Beyond that data should be passed from form to data object. If that happens to result in it also being passed from the data object back to another form, then woo hoo.

Yes I did read that you weren't getting into form synchronization, and that's cool. But the fact is the world runs on data. And every day it more and more becomes a case of GUI talks to database... GUI talks to website... GUIs (form or WPF window) is supposed to be nothing more than a way to interact with your data. Form to form is just wrong, and doesn't allow any kind of scalability for things like: clerkForm, supervisorForm, customerForm... all looking at different levels of customer data - just as one example.

If rookies get this concept down from the beginning they don't have to be re-trained, and don't have to break all sorts of bad habits and bad thinking.
Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2