Connecting the New Form to the Application

We have ClientList and ClientForm. When we run the application, it opens to ClientList, showing us a list of records in a data view grid. We would like to double-click in the list to open ClientForm. You’ve probably seen this behavior in many applications.

Open ClientList in the form designer. Select the data view grid, and switch from viewing properties to viewing events. Going down the list, there is nothing like RowDoubleClick, but there are three cell events from which to choose:

  • CellMouseDoubleClick – reacts to mouse click anywhere in the cell
  • CellContentDoubleClick – reacts to mouse click on cell content, not black space in cell
  • CellDoubleClick – reacts to mouse or keyboard click anywhere in the cell

Let’s choose CellDoubleClick. Double-click in the event to open the editor and create a method stub for us.

private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{

}

At this point, we want to open the other form, ClientForm. Two lines of code will do it.

private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
	var frm = new ClientForm();
	frm.Show();
}

We run the application, and hey, it works! Well, sort of.

When the double-click a row in ClientList, the instance of ClientForm we create and show does indeed appear. But we expected it to appear positioned at the row we double-clicked on in the ClientList. What’s going on?

Remember how we dragged the Clients table onto ClientForm, and it automatically created several data objects, like the table adapter and the binding source? Well, those instances were completely independent and separate from the data objects created in ClientList. One form is launching the other, but nothing connects the data between the two. And by they way, let’s call ClientForm the parent form, and ClientList the child list. There can be only one parent instance, but there can be several child instances.

This is a more confounding problem than you’d think. StackOverflow and other forums are full of questions about how best to do this. We want the connection to be such that changes made in one form are reflected immediately in other form, without having to reload data. But we also want independent record pointers so that various forms can navigate the shared data. Here’s how I solved this.

// in ClientList
private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
	var frm = new ClientForm(dSClients, clientsBindingSource);
	frm.Show();
}

Over in ClientForm, we have to make the code look like this:

using System.Windows.Forms;

namespace TheBooks
{
    public partial class ClientForm : Form
    {
        public ClientForm(DSClients ds, BindingSource bs)
        {
            InitializeComponent();
            dSClients = ds;
            clientsBindingSource.DataSource = bs.DataSource;
            clientsBindingSource.DataMember = "Clients";
            clientsBindingSource.Position = bs.Position;
            clientsBindingSource.Sort = bs.Sort;
            clientsBindingSource.Filter = bs.Filter;
        }

        private void ClientForm_Load(object sender, System.EventArgs e)
        {
            // TODO: This line of code loads data into the 'dSClients.Clients' table. You can move, or remove it, as needed.
            //this.clientsTableAdapter.Fill(this.dSClients.Clients);

        }

        private void clientsBindingNavigatorSaveItem_Click(object sender, System.EventArgs e)
        {
            this.Validate();
            this.clientsBindingSource.EndEdit();
            this.tableAdapterManager.UpdateAll(this.dSClients);
        }

First, we pass the parent’s data source and binding source to the child. Then we add some code to the form’s constructor, right after the call to InitializeComponent. You know InitializeComponent’s job is to execute the code in the designer file, which will create the form and the data objects, so if we want to make changes, they have to come after the call to InitializeComponent.

Note that we refer to dSClients and clientsBindingSource as if they had been previously created, and they were — when we dropped the data source onto the form in Designer, and Visual Studio created the data objects.

In C#, when we assign an object to another object, the object isn’t copied like it happens with ordinary variables. Instead, both names now refer to the same object. Thus, when we assign ds to dSClients, we are simply setting dSClients to “point” to ds, which is passed from the parent form.

With the binding source, it’s a little different. We want the binding source to act on the same data, but we need it to be independent from the binding source in the parent form. Thus, instead of assigning the whole of the passed argument bs, we assign only certain properties.

FInally, remember that Fill method that loaded the table adapter? We must remove that line because we are no longer loading data. The data set received from the parent form is already loaded.

To make sure a detail I miss doesn’t prevent anyone from getting to this point, here’s a zip file with all the source code so far and the SQLite database.

Leave a Reply

Your email address will not be published. Required fields are marked *