Passing data between two Forms in WinForms

Passing data between two Forms, or any two classes, is a very common issue in C#. If you need to pass data around, there are a few ways to do it. Let's get it to work, and then do it better.

Passing data between two Forms in WinForms

Yes, WinForms is an older technology, and no, there's nothing particularly sexy about it. But it works and it's quick to spin up a new program. Many businesses still have legacy apps built with it, and its drag-and-drop interface makes it easy to use in a classroom setting too.

True to its name, most things you do in WinForms revolve around Forms, and the creation of multiple Forms for performing different functions. Functions like collecting data from the user, or displaying a record from the database for the user to edit. After the user enters the requested data, or makes changes to that record, and presses your OK button to close the current Form, their changes are lost unless you do something with them first.

You might want to save the changes to a database at this point, but more often you'll just want to pass the data back to the original Form that created the one that was just closed.

Note: All the following code is available on Github.


How do I pass data between two Forms?

I see this question asked a lot, and it's usually one of these slight variations:

  • How do I pass data between two forms?
  • How do I access data from Form1 (or Form2) in Form2 (or Form1)?
  • How do I get the contents of a <TextBox / List / etc> from another form?
  • How do I enter data into a form, and then close it and display it in my first form?

Let's define our environment

Every program's different, but this particular issue has a few practical, common solutions. Let's mockup a question first - this is pretty typical of the questions I see:

I have two Forms. The first Form has a button for opening the second Form.
The second Form collects input from the user in some TextBox controls. When they're done and close it, I need to pass those values back to the first Form.
Here's is my code but it's not working as expected. How do I get what the user entered back into the first Form?
public class Form1 : Form
{
    public Form1() { }
 
    private void btnGetUserInput_Click(object sender, EventArgs e)
    {
        Form2 form2 = new Form2();
        form2.ShowDialog();
    }
}
 
public class Form2 : Form
{
    public Form2() { }
 
    private void btnSaveInput_Click(object sender, EventArgs e)
    {
        Form1 form1 = new Form1();
        form1.???  // How do I show my values on the first form?
        form1.ShowDialog();
    }
}

This is a very basic example. The first Form displays the data collected in the second Form, maybe in a DataGridView or just some Labels. Or maybe the first Form takes some action on the data from the second Form, like saving it to a file. Whatever. Here's the app in all it's glory.

basic 2form app

So our mock environment is:

  • C#, WinForms
  • Two Forms, Form1 and Form2
  • Form1 starts with the program, and contains a DataGridView and a Button for opening Form2
  • Form2 has a couple TextBox controls for accepting user input, and a button to close the Form and “Save” the input

Reference the original Form

A Form is just another class, and in order to change the properties of any class, you need to have a reference to the particular instance you wish to modify.

Look at the code in the btnSaveInput_Click() event method above. If you're trying to create a new instance of a Form in order to update it, you're making a fundamental mistake about how class instantiation works. Multiple instances of a class do not automatically share data between them.

In other words, when you create a second instance of Form1 inside of Form2, it has nothing to do with the original instance of Form1 that you started out with. This second instance of Form1 will go out of scope and become inaccessible when the btnSaveInput_Click() method ends, and the values you set in it will not carry over to the original Form1.

(You might be tempted to define public static fields that are easily accessible from everywhere, but try to avoid it. The natural progression is then a static class to hold those static fields, and pretty soon you have a blob often called the god class that's nigh impossible to debug. Been there, seen that.)

You have two practical choices for getting the data back into the first Form (Form1):

  • While you're still in Form2, push the data back to Form1.
  • Once you return to Form1, pull the data from Form2.

Pushing data back to Form1

First, I'll explain how you could do this, then I'll explain why you shouldn't.

In order to push data back to Form1, you need a reference to it. In general, the easiest way to do that is to pass a reference in the constructor when you instantiate Form2:

public partial class Form2 : Form
{
    public Form1 form1;
 
    public Form2(Form1 form1)
    {
        InitializeComponent();
        this.form1 = form1;
    }
 
    ...

Now you can access public properties, methods, whatever on the first Form.

public partial class Form1 : Form
{
    public Form1() { }
 
    private void btnGetUserInput_Click(object sender, EventArgs e)
    {
       // Notice the 'using' statement. It helps ensure you clean up resources.
       using (var form2 = new Form2(this))
       {
           form2.ShowDialog();
       }
    }
 
    public void SetName(string name)
    {
        lblName.Text = name;
    }
 
    public int Age
    {
        set { lblAge.Text = value.ToString(); }
    }
}
 
public partial class Form2 : Form
{
    private Form1 form1;
 
    public Form2(Form1 form1)
    {
        InitializeComponent();
        this.form1 = form1;
    }
 
    private void btnClose_Click(object sender, EventArgs e)
    {
        form1.SetName(txtName.Text);
 
        int age;
        if (int.TryParse(txtAge.Text, out age))
            form1.Age = age;
 
        this.Close();
    }
}

Issue #1: Reusability

The first issue I have with this method is reusability. Imagine that next week, you want to use Form2 from another Form, say Form3. You want to collect the same data but present it in a different manner. Now your Form isn’t as reusable, because it's not so clear who will be calling the Form.

Issue #2: Too much knowledge

In general, a thing being called should know little to nothing about the thing calling it.

There is no reason for Form2 to know about the other Forms, user controls, class libraries, etc that could potentially use it. You should not have a bunch of if/else statements to handle this kind of logic. Imagine the following, where we have two Forms (an employee Form and a student Form), and an additional class library with no UI of its own, all using the Details Form to get name and age.

public partial class StudentForm : Form
{
    private void btnGetStudentData_Click(object sender, EventArgs e)
    {
        using (var detailForm = new DetailForm(this))
        {
            detailForm.ShowDialog();
        }
    }
 
    public void SetStudentName(string name)
    {
        lblStudentName.Text = name;
    }
}
 
public partial class EmployeeForm : Form
{
    private void btnGetEmployeeInput_Click(object sender, EventArgs e)
    {
        using (var detailForm = new DetailForm(this))
        {
            detailForm.ShowDialog();
        }
    }
 
    public string EmployeeName { get; set; }
}
 
public class SomeClass
{
    public void SomeMethod()
    {
        using (var detailForm = new DetailForm(this))
        {
            detailForm.ShowDialog();
        }
    }
 
    public string Name { get; set; }
}
 
public partial class DetailForm : Form
{
    private StudentForm studentForm;
    private EmployeeForm employeeForm;
    private SomeClass someClass;
 
    public DetailForm(StudentForm form)
    {
        InitializeComponent();
        studentForm = form;
    }
 
    public DetailForm(EmployeeForm form)
    {
        InitializeComponent();
        employeeForm = form;
    }
 
    public DetailForm(SomeClass someClass)
    {
        InitializeComponent();
        this.someClass = someClass;
    }
 
    private void btnClose_Click(object sender, EventArgs e)
    {
        if (studentForm != null)
            studentForm.SetStudentName(txtName.Text);
        else if (employeeForm != null)
            employeeForm.EmployeeName = txtName.Text;
        else if (someClass != null)
            someClass.Name = txtName.Text;
 
        this.Close();
    }
}

The DetailForm Form knows way too much about what's potentially calling it, and has to plan for every possible caller - things get ugly quick. Also, making changes to one of the callers requires changing the shared Form as well.

Let's look at another way...

Pulling data from Form2

A better option is to make the data available from the second Form, then let the individual callers retrieve the data (or not) as they choose. The easiest way to do this is to create public "getter" methods on the second Form, which I'll show below.

(If you're unfamiliar with getter/setter properties, check out this thread - it's 10 years old and for Java, but the reasoning is the same for C# and remains relevant even today.)

Here's a copy/paste of the previous code block, modified so that the shared Form knows nothing about its callers.

public partial class StudentForm : Form
{
    private void btnGetStudentData_Click(object sender, EventArgs e)
    {
        using (var detailForm = new DetailForm())
        {
            detailForm.ShowDialog();
 
            lblStudentName.Text = detailForm.Name;
        }
    }
}
 
public partial class EmployeeForm : Form
{
    private void btnGetEmployeeInput_Click(object sender, EventArgs e)
    {
        using (var detailForm = new DetailForm())
        {
            detailForm.ShowDialog();
 
            EmployeeName = detailForm.Name;
        }
    }
}
 
public class SomeClass
{
    public void SomeMethod()
    {
        using (var detailForm = new DetailForm())
        {
            detailForm.ShowDialog();
 
            Name = detailForm.Name;
            Age = detailForm.Age;
        }
    }
 
    public string Name { get; private set; }
    public string Age { get; private set; }
}
 
public partial class DetailForm : Form
{
    public DetailForm()
    {
        InitializeComponent();
    }
 
    public string Name
    {
        get { return txtName.Text; }
    }
 
    public int Age
    {
        get
        {
            int result;
            Int32.TryParse(txtAge.Text, out result);
            return result;
        }
    }
 
    // Even this could be removed, if you set this button as the Accept
    //  button on the Form, or set a DialogResult value on the button
    private void btnClose_Click(object sender, EventArgs e)
    {
        this.Close();
    }
}

The second Form is left cleaner. It requires no special knowledge of its callers, and has greater reusability and maintainability. The callers can grab as much or as little data as they need, or do nothing at all.

You can use this method to retrieve whatever data you need from the child Form (Form2), such as a List or TextBox values.


More Reading

I hope this helped clarify a few things. If it still seems unclear, or you see a possible error somewhere, please leave a comment below and we’ll figure it out!

This is a really common question for beginners. You can do a search for "passing between forms winforms" and you'll get quite a few hits. Here are a few good ones. I haven't read them all through end-to-end, but they're thorough and will give you something to think about, at least.

Note: The code in this post is also available on Github.


Photo by Hal Gatewood