Wednesday, June 29, 2011

Simple Binding in Windows Froms Using DataGridView's Row_Enter Event

I find it much more convenient to insert/edit/delete data from user interface elements (text boxes, checkboxes, numeric updowns, list boxes, comboboxes, etc.) rather than directly from the DataGridView.
I use the DataGridView to display the records with only few properties (columns) and automatically display the complete record data inside the aforementioned user interface elements for the currently selected row in the DataGridView.
After trying many events that are raised while interacting with the DataGridView, I found the Row_Enter event of the DataGridView class to be the solution to my simple databinding needs in many projects. The solution applies to Visual Studio 2008 and 2010.
The database used for this demo is the MS’s famous database Northwind. I have created a Business Object Class called Customer and a class CustomerList which, obviously, represents a list of Customer objects.
The whole data access work is done inside the class called CustomerDB. It reads the connection string from the app.config file, executes the query to get all the records from the “dbo.Customers” table in Northwind via a DataReader, and returns a list of Customer objects to the caller.
Please be aware that the Business Object Classes (Customer and CustomerList), and Data Access Class (CustomerDB) in a well-architected systems should probably be placed in separate layers called Business Object Layer, and Data Access Layer respectively. (If you need an intro on how to build n-layered apps, please read this article http://imar.spaanjaars.com/476/n-layered-web-applications-with-aspnet-35-part-1-general-introduction  by Imar Spanjaars . Though it has been written for ASP.NET, most of the concepts apply to windows forms as well.)
Once the Customer and CustomerList classes are complete, its very easy to create a BindingSource to tell the DataGridView where is the data coming from (see the steps below):
Fig. 1 Click on the little black arrow on the right top corner of the DataGridView shown in design-time.


                                                     Fig. 2 Click on “Add Project Data Source”  


                                           Fig. 3 Select the “Object” button and click “Next”




                        Fig. 4 Check the box “CustomerListObject” from the list and click “Finish”.


When you complete these steps Visual Studio will immediately map all the Customer class’ properties to DataGridView Columns and will show them in design time. You still have a choice to control which columns will be visible in the grid by modifying the DataGridView’s “Columns” property from Visual Studio.



Fig. 7 Modify the visibility of columns by clicking on Columns “Collection” of the DataGridView.

The data is retrieved and bound to the DataGridView in the Form’s Load event:
//*************************************************************
private void FormSimpleBinding_Load(object sender, EventArgs e)
{
       this.dataGridView.DataSource = CustomerDB.GetList();

       AddSimpleBinding();

       this.WindowState = FormWindowState.Maximized;
}


//*****************************
private void AddSimpleBinding()
{
     if (this.lblCustomer_Id.DataBindings.Count == 0)
     {
          if (this.dataGridView.Rows.Count > 0)
          {
              this.lblCustomer_Id.DataBindings.Add("Text", this.dataGridView[0, 0], "Value", false);

              this.txtCompanyName.DataBindings.Add("Text", this.dataGridView[1, 0], "Value", false);

              this.txtContactName.DataBindings.Add("Text", this.dataGridView[2, 0], "Value", false);

              this.txtContactTitle.DataBindings.Add("Text", this.dataGridView[3, 0], "Value", false);

              this.txtAddress.DataBindings.Add("Text", this.dataGridView[4, 0], "Value", false);

              this.txtCity.DataBindings.Add("Text", this.dataGridView[5, 0], "Value", false);

              this.txtRegion.DataBindings.Add("Text", this.dataGridView[6, 0], "Value", false);

              this.txtPostalCode.DataBindings.Add("Text", this.dataGridView[7, 0], "Value", false);

              this.txtCountry.DataBindings.Add("Text", this.dataGridView[8, 0], "Value", false);

              this.txtPhone.DataBindings.Add("Text", this.dataGridView[9, 0], "Value", false);

              this.txtFax.DataBindings.Add("Text", this.dataGridView[10, 0], "Value", false);
         }
      }
   }

The method AddSimpleBinding() does the simple binding between the DataGridView’s first row (with index 0) and the GUI elements (a Label and TextBoxes.) It first checks if the UI element is already bound:
if (this.lblCustomer_Id.DataBindings.Count == 0)

 This check is necessary because an attempt to bind to a particular element more than once throws an exception.
                                      
Fig. 5 The intital Binding done in FormSimpleBinding_Load Event Handler binds the first row of the DataGridView to the UI elements.


When the user selects a particular row in the DataGridView, the RowEnter event is raised and in this event’s handler we have to:
  -          Remove the existing bindings  
  -          Rebind the current row to the UI elements 
//****************************************************************************
private void dataGridView_RowEnter(object sender, DataGridViewCellEventArgs e) 
{ 
       RemoveSimpleBindings();
       AddSimpleBinding(e.RowIndex); 
} 

//********************************* 
private void RemoveSimpleBindings() 
{
   if (this.lblCustomer_Id.DataBindings.Count > 0) 
   { 
     this.lblCustomer_Id.DataBindings.RemoveAt(0);
     this.txtCompanyName.DataBindings.RemoveAt(0);
     this.txtContactName.DataBindings.RemoveAt(0);
     this.txtContactTitle.DataBindings.RemoveAt(0); 
       this.txtAddress.DataBindings.RemoveAt(0); 
     this.txtCity.DataBindings.RemoveAt(0);
     this.txtRegion.DataBindings.RemoveAt(0); 
       this.txtPostalCode.DataBindings.RemoveAt(0); 
     this.txtCountry.DataBindings.RemoveAt(0);
     this.txtPhone.DataBindings.RemoveAt(0); 
       this.txtFax.DataBindings.RemoveAt(0);  
  }
} 

//******************************************** 
private void AddSimpleBinding(int intRowIndex)
{  
   if (this.lblCustomer_Id.DataBindings.Count == 0) 
   {
       if (intRowIndex >= 0)  
       { 
           this.lblCustomer_Id.DataBindings.Add("Text", this.dataGridView[0, intRowIndex], "Value", false);
            this.txtCompanyName.DataBindings.Add("Text", this.dataGridView[1, intRowIndex], "Value", false);           
           this.txtContactName.DataBindings.Add("Text", this.dataGridView[2, intRowIndex], "Value", false);  
           this.txtContactTitle.DataBindings.Add("Text", this.dataGridView[3, intRowIndex], "Value", false);
            this.txtAddress.DataBindings.Add("Text", this.dataGridView[4, intRowIndex], "Value", false);      
           this.txtCity.DataBindings.Add("Text", this.dataGridView[5, intRowIndex], "Value", false); 
           this.txtRegion.DataBindings.Add("Text", this.dataGridView[6, intRowIndex], "Value", false);
            this.txtPostalCode.DataBindings.Add("Text", this.dataGridView[7, intRowIndex], "Value", false);        
           this.txtCountry.DataBindings.Add("Text", this.dataGridView[8, intRowIndex], "Value", false); 
           this.txtPhone.DataBindings.Add("Text", this.dataGridView[9, intRowIndex], "Value", false);
           this.txtFax.DataBindings.Add("Text", this.dataGridView[10, intRowIndex], "Value", false);                   
       } 
        else  
      {
           this.lblCustomer_Id.DataBindings.Add("Text", this.dataGridView[0, this.dataGridView.CurrentRow.Index], "Value", false);         
         this.txtCompanyName.DataBindings.Add("Text", this.dataGridView[1, this.dataGridView.CurrentRow.Index], "Value", false); 
         this.txtContactName.DataBindings.Add("Text", this.dataGridView[2, this.dataGridView.CurrentRow.Index], "Value", false);
           this.txtContactTitle.DataBindings.Add("Text", this.dataGridView[3, this.dataGridView.CurrentRow.Index], "Value", false); 
           this.txtAddress.DataBindings.Add("Text", this.dataGridView[4, this.dataGridView.CurrentRow.Index], "Value", false); 
        this.txtCity.DataBindings.Add("Text", this.dataGridView[5, this.dataGridView.CurrentRow.Index], "Value", false);
          this.txtRegion.DataBindings.Add("Text", this.dataGridView[6, this.dataGridView.CurrentRow.Index], "Value", false);  
        this.txtPostalCode.DataBindings.Add("Text", this.dataGridView[7, this.dataGridView.CurrentRow.Index], "Value", false);  
        this.txtCountry.DataBindings.Add("Text", this.dataGridView[8, this.dataGridView.CurrentRow.Index], "Value", false); 
        this.txtPhone.DataBindings.Add("Text", this.dataGridView[9, this.dataGridView.CurrentRow.Index], "Value", false);
        this.txtFax.DataBindings.Add("Text", this.dataGridView[10, this.dataGridView.CurrentRow.Index], "Value", false); 
        } 
  }
} 
First, it must be checked that a row is clicked. If a row is clicked or navigated to by the keyboard, the current row index is 0 or a positive number. If a mouse click happens outside the Rows then the current row index is -1.  
When a valid row index is passed the binding is done like this:

this.lblCustomer_Id.DataBindings.Add("Text", this.dataGridView[0, intRowIndex], "Value", false);

On the other hand, when -1 is passed binding is done by:

this.lblCustomer_Id.DataBindings.Add("Text", this.dataGridView[0, this.dataGridView.CurrentRow.Index], "Value", false);
 
The result is shown below:


Fig. 6 The DataGridView bound to a Label and TextBoxes
 

No comments:

Post a Comment