Tuesday, December 21, 2010

How to Get Values From Form1 to Form2 (or any other forms, for that matter).

This question is asked practically every day on C# Forums: "how do I get a value from Form1 to Form2?" or the other way around. There is a shocking amount of bad advice out there on the internet about how to go about doing this. This tutorial is intended to show you more "correct" ways of passing data between forms.

The scope of this tutorial is limited to passing data between forms. We will look at both properties and custom events, but in a brief overview.

There are already some excellent tutorials that cover some of these topics in more detail, should you want more information:
Quick and easy custom events
Bulding an application - Part 1
Building an application - Part 2

Let's start with a new Windows Forms project (though note, these methods can also be used for WPF forms as well). I'm creating three forms: DefaultForm, FirstNameForm, and LastNameForm. DefaultForm is "Form1", the Form that is opened when the application starts.

There are two types of child form: modal and modeless (non-modal). There are different techniques for getting information from both, since they behave differently.

Modeless child windows do not require an immediate response, and do not block you from focusing the parent form again. You are allowed to freely switch back and forth between them. An example of a modeless dialog would be the "Find" (ctrl+F) dialog in Visual Studio.

Modal dialogs, on the other hand, block you from switching back to the parent window. They require a response before continuing. An example of a modal dialog is a MessageBox.

Depending on which type of form you choose to use, you will need to get the data from it differently.

Modeless

First, we'll visit modeless forms. A form is treated as modeless when it is opened by the .Show method. Execution does not stop on that line; the code after it continues to execute even though the child form is now shown and focused.

We'll use FirstNameForm as the modeless dialog.

Since execution does not stop, we need a way to know when the value we want is updated. Custom events are the answer. The reason we like to use events rather than just passing properties around is that we don't want to tightly couple our forms. DefaultForm doesn't need to know anything more about FirstNameForm than it should. This way, it doesn't have to know anything about it other than that it has one event. It doesn't need to know names of properties, and it doesn't have to guess about when the properties are updated.

Another benefit is reusability. You may want several forms to update themselves when a child window does. This way, each form can subscribe to the event and handle it the way they choose. As another expert on DIC explains, it's like bingo. When a number is drawn, the guy doesn't go down the aisle and mark each person's board. He calls it out (an event) and each person handles it accordingly (the subscribers handling the event).

Let's create a custom EventArgs class to suit our needs. This simple class extends EventArgs, and has a public string property called Name.
public class NameUpdatedEventArgs : EventArgs
{
    public string Name { get; set; }
}

We'll use that EventArgs class to pass our value with. Now, we must create the event itself. Inside the class definition for FirstNameForm, we'll add the folowing code:
public event EventHandler<NameUpdatedEventArgs> FirstNameUpdated;

protected virtual void OnFirstNameUpdated(NameUpdatedEventArgs e)
{
    if (FirstNameUpdated != null)
        FirstNameUpdated(this, e);
}

The first line creates the event itself. The method that follows actually triggers the event itself. It expects you to pass it a NameUpdatedEventArgs, which it will use as one of the event's parameters. So when we want our FirstNameUpdated event to be triggered, we call our OnFirstNameUpdated method.

Now that we have the code for an event up, we need to add the logic to call that event. In my example, we're going to update the value when the user clicks a button:
private void UpdateButton_Click(object sender, EventArgs e)
{
    string newFirstName = firstNameTextBox.Text;
    NameUpdatedEventArgs nuea = new NameUpdatedEventArgs();
    nuea.Name = newFirstName;
    OnFirstNameUpdated(nuea);
}

First, we're getting the new first name. Then, we create a new NameUpdateEventArgs object. Then, we assign the new first name to the Name property of the args. Lastly, we call the method that triggers the FirstNameUpdated event.

Now we're done with FirstNameForm. It's set up to broadcast its event, and needs nothing else to work.

In the DefaultForm, we need to handle the event. In this example, we are going to open the child window as soon as we open the DefaultForm, so we'll create and show the FirstNameForm in DefaultForm's constructor:
public DefaultForm()
{
    InitializeComponent();
    FirstNameForm fnf = new FirstNameForm();
    fnf.FirstNameUpdated += new EventHandler<NameUpdatedEventArgs>(fnf_FirstNameUpdated);
    fnf.Show();
}

The first line of the method is the standard method call that Visual Studio adds to every form to set up all the GUI components. The next line creates a new instance of FirstNameForm.
The next line adds an event handler**. And the last shows the window in a modeless way.

**A quick note on adding event handlers: Visual Studio will do most of the work for you. Type the beginning of the line up to the +=:



And Visual Studio asks you to push TAB. Push it:



And it finishes the line for you. But it wants you to push tab again. Do it:



And it creates the handler method for you as well. It really does take the guesswork and memorization out of making event handlers.

That method that VS created for us will be the code that executes every time that FirstNameUpdated is triggered. Let's fill it in:

void fnf_FirstNameUpdated(object sender, NameUpdatedEventArgs e)
{
    if (e != null && e.Name != null)
        firstNameLabel.Text = e.Name;
}

Remember that we added the name to our NameUpdatedEventArgs? That's in the variable e. First we check to make sure e and it's property are not null, then we take the Name value from e and assign it to a label.

And that's that. When we run the program, we see this before the update:



And after update:



The FirstNameDialog will not go away until you close it.

Modal

Next, we'll visit modeless forms. A form is treated as moda; when it is opened by the .ShowDialog method. Execution does stop at that line until the child form is closed. The ShowDialog method returns a DialogResult object.

We'll use LastNameForm as the modal dialog.

Now, modal dialogs are a bit different of a model from the other types. They're usually more limited in their focus and purpose, and execution of the parent form is blocked while they're open,\ so it's OK to tightly couple them with a parent form. To an extent. Neither form should know about the UI controls on the other, of course. That's some of the worst advice that's common on the web "make your TextBox public." Don't do that!. Instead, wrap your TextBox's Text property in a public Property. Here's how we would do this in LastNameForm (this would be added inside the class LastNameForm):

public string LastName
{
    get
    {
        return lastNameTextBox.Text;
    }
}

Now instead of dealing with an entire TextBox, we're just dealing with a string.

Now, since we don't need a way to know when the child form is closed (because ShowDialog is a blocking call), we just need to make sure to close the form when we get what we want. In this example, I'm closing the form when someone clicks a button labeled "OK":

private void OkButton_Click(object sender, EventArgs e)
{
    this.DialogResult = DialogResult.OK;
    this.Close();
}

We're setting the DialogResult to OK to let the other form know it wasn't forced closed or cancelled.

And that's all we have to do for LastNameDialog. Now we need to look at creating the dialog and retrieving it's value.

In DefaultForm, we'll launch a LastNameDialog when a user clicks a button:

private void GetLastNameButton_Click(object sender, EventArgs e)
{
    LastNameForm lnf = new LastNameForm();
    DialogResult res = lnf.ShowDialog();
    if (res == DialogResult.OK)
        lastNameLabel.Text = lnf.LastName;
}

The first line creates a new LastNameForm. The next calls it as a Modal dialog, and retrieves its response once it's closed. The next lines test to see if the DialogResult was "OK", and if so, uses the value of the LastName property for a label on the DefaultForm.

And that's really all there is when it comes to modal dialogs. Remember, this way is less involved, but Modal dialogs block execution of the main form, and you can't switch from one to the other. So you must make your choice of which to use wisely.

Here's how it looks when using it. Before clicking OK:



After clicking OK:



Note that the dialog went away when we clicked OK.

Passing a value to a new Form

Another very common related question involves passing values to a new form. One very easy way to do this is to remember that Forms are just Classes, and that all classes have constructors. Calling new Form1() is really no different than new Random() or any other class. And because this form is a class you are creating, you have control over the constructor, and its parameters.

I'll continue on with the previous example. We'll add a form called DisplayNameForm. This form is simply a display form, not an input form, so we don't have to worry about events or anything like that. But we need it to display the name, so it needs both names when it's created. Normally, when a form is created, it's created with a default constructor:

public DisplayNameForm()
{
    InitializeComponent();
}

But just because it starts that way doesn't mean it has to stay that way. Let's add some parameters to the constructor, and some assignment logic.

public DisplayNameForm(string firstName, string lastName)
{
    InitializeComponent();
    firstNameLabel.Text = firstName;
    lastNameLabel.Text = lastName;
}

Now the form requires both a first name and a last name to be created. And when it's created, it assigns those values to some labels on the form.

This, of course, changes the way we have to create and call the form. For this example, in DisplayForm, I'm showing this DisplayNameForm on a button click:

private void DisplayNameButton_Click(object sender, EventArgs e)
{
    string firstName = firstNameLabel.Text;
    string lastName = lastNameLabel.Text;
    DisplayNameForm dnf = new DisplayNameForm(firstName, lastName);
    dnf.Show();
}

The first two lines gather the names. The third is the actual creation of the new form. Notice that we're providing the names as parameters to the constructor. They match the parameters in the constructor's definition. Lastly, we show it like any other form.

Conclusion

I hope you have enjoyed this tutorial. I've tried to make it as simple as possible while still following sound design and object oriented principles.

This article was crossposted from DreamInCode.Net and Bytes.Com.

2 comments:

  1. Very helpful post. I tried the modal form and couldn't get the value passed back. At what point does the defaultForm retrieve the string? Isn't the lnf already closed by then and the value gone?

    In debugging this, I never see this part executed:
    return lastNameTextBox.Text;

    Thanks,
    Link

    ReplyDelete
  2. Ah - I showed a messagebox with the value received and it is actually received. But, it just doesn't show up in the textbox. Do I need to refresh the values in the calling form somehow?

    ReplyDelete

Speak your mind.