programming4us
programming4us
DATABASE

.NET Compact Framework 3.5 : Working with Data Sets (part 2) - Data Binding

6/30/2012 3:13:34 PM

2. Data Binding

Data binding is the ability to associate an object that holds data with a control that displays and updates that data. 

  • The DataGrid control can display the entire contents of a bound data object.

  • You bind to a DataGrid control by setting its DataSource property.

  • Unlike the desktop DataGrid control, the .NET Compact Framework version is read-only. 

  • The ListBox and ComboBox controls can display one column of a DataTable/DataView and can use one other column for an identifying key.

  • You can bind to a ListBox or ComboBox control by setting its DataSource, DisplayMember, and ValueMember properties.

  • Unlike the desktop ComboBox control, the .NET Compact Framework version is read-only. That is, the user can select from the list of choices but cannot enter text into the ComboBox control.

  • Single-item controls, such as the TextBox, Label, RadioButton, and CheckBox, can bind to a single data element of a data object.

  • You can bind to a single-item control by adding entries to its DataBindings collection.

  • The DataBindings collection specifies the column to be bound to but not the row. A “current” row of a DataTable/DataView is always used for binding.

  • Data binding is a two-way street. Changes to data made in the control are automatically pushed back to the bound data table.

To lead into our upcoming discussion of data binding, we add the following information to our list.

  • Every DataTable object has an associated DataView object that can be used for data binding. Additional views can be created for each table, but one view is always present once the table has been created.

  • DataTable objects do not really have a “current” row. Instead, the DataTable’s CurrencyManager object, located within the form’s BindingContext property, must be used to position the DataTable to a row.

We begin by writing a very simple application to illustrate the benefits and issues of data binding. We use a very simple SQL Server CE database to populate the data set. The tables in the data set are the Categories and Products tables; the relationship between them has been defined within the data set by adding a DataRelation object named FKProdCat to the data set. Once the data set has been populated, we use data binding to display the information in the tables to the user. The focus here is on the binding of data between the data set and the controls, not on the movement of data between the data set and the database.

The first form, shown in Figure 1, consists of two DataGrid controls, one for displaying the Categories rows and one for displaying the Products rows of whichever Category the user selects. In other words, we want to reflect the parent/child relationship between categories and products on the form.


Figure 1. The Parent/Child Form


The second form, shown in Figure 2, displays one product at a time in TextBox controls and provides the user with a variety of ways to specify the desired product. It also allows the user to update the product information.

Figure 2. The Single-Item Form


The two forms are not related in a business sense; we are simply using one to illustrate multi-item data binding and the other to illustrate single-item data binding.

The first form is the simpler to program, as we shall see, for it binds data tables to multi-item controls.

2.1. Binding to Multi-Item Controls

The form shown in Figure 6.3 declares a private variable to hold the data set named dsetDB; creates a new data set, storing the reference in dsetDB; and loads dsetDB with data from the SQL Server CE database. At this point, we have a data set that contains two data tables and the relationship between them.

To reflect that relationship on the form, we bind the upper DataGrid control to the Categories data table so that the entire table is displayed within the DataGrid control. We bind the lower DataGrid control to the default view of the Products table because we can easily update the view whenever the user selects a new category. Here is the data-binding code:

private void mitemDisplayDS_Click(object sender, EventArgs e)
{
   //  Display the Categories and Products tables
   //     in the parent and child DataGrids.
   dgridParent.DataSource =
      dsetDB.Tables["Categories"];
   dgridChild.DataSource =
      dsetDB.Tables["Products"].DefaultView;
}

Whenever the user selects a new category, the application reacts to the CurrentCellChanged event by setting the row filter for the Products view to select only products of that category, as shown in this code:

private void dgridParent_CurrentCellChanged(object sender,
EventArgs e)
{
   DataTable dtabParent = (DataTable)dgridParent.DataSource;
   DataView dviewChild = (DataView)dgridChild.DataSource;
   dviewChild.RowFilter =
      "CategoryID = " +
      dtabParent.Rows[dgridParent.CurrentRowIndex]["CategoryID"];
}

Thus, the DataView class is the key piece in reflecting the parent/child relationship to the user.

2.2. Binding to Single-Item Controls

When asked to display the second form shown in Figure 6.4 the first form stores a reference to the data set in an Internal variable so that the second form can retrieve it and then displays the second form. The second form displays one product row at a time, using Label and TextBox controls. This makes it inherently more complex than the first form. Binding to single-item controls is more difficult than binding to multi-item controls for three reasons.

  1. The user must be given a mechanism to specify which product should be displayed.

  2. The TextBox controls must display the specified row.

  3. The TextBox controls can be used to update data as well as display it.

Data binding to single-valued controls, such as TextBox controls, requires some additional design decisions. Making good decisions requires an understanding of data-binding internals. So, let’s look at the following decisions that must be made and the impacts of each.

  1. How does the user designate which row is to be displayed?

    1. By matching binding. Bind the data object to a multi-item control, such as a ComboBox or DataGrid, as well as to the single-item controls, such as the Label and TextBox controls. The user selects the desired row from the multi-item control.

    2. By indexing. Provide a scroll bar or some other position control. The user indicates the relative position of the desired row within the data object. The application positions to that row.

    3. By search key. Provide a text box. The user enters a value. The application searches the data object for the row containing the entered value.

  2. How should that row be assigned to the control?

    1. By current row. The application designates the desired row as the current row of the data object.

    2. By data view. The application binds the single-item controls to the data table’s DefaultView object and then sets the view’s Filter property to limit the view to just the desired row.

  3. When should the data table be updated with the values from the single-item controls?

    1. When the user moves to a new field? No. It is not necessary to update the data object if the user is continuing to modify other fields of the same row; wait until the user moves to a new row.

    2. When the user positions to a new row? No. Data binding will automatically update the old row values whenever the user moves to a new row.

    3. When the user indicates that he or she is finished with the edit (e.g., by using a Cancel or Update button or by closing the form)? Yes. You need to use the data table’s CurrencyManager object to complete or cancel the update of the current row if the user exits the operation without moving to a new row.

The following subsections discuss these three decisions in more detail.

2.3. Designating the Row to Be Displayed

Figure 3 shows the single-item form providing all three methods mentioned previously for designating which row to display. We use this form to cover the issues being addressed here. It’s not the most beautiful form we ever designed, but it does illustrate the functionality we want to cover.

Figure 3. Three Ways to Designate Which Row to Display


Matching Binding

Of the three designation methods, the easiest to program is matching binding. Once you bind the single-item controls and the multi-item controls to the same data object, you no longer have to decide how to assign the row to the single-item controls because the user’s selection is automatically reflected in the single-item controls. In our example, the binding is done in the form’s Load event handler, as shown here:

//  Bind the ComboBox with the Product names.
comboProductIDs.DisplayMember = strPKDesc;
comboProductIDs.ValueMember = strPKName;
comboProductIDs.DataSource = dtabProducts;
comboProductIDs.SelectedIndex = 0;

//  Bind the DataTable's columns to the text boxes.
textProductID.DataBindings.Add
   ("Text", dtabProducts, strPKName);
textProductName.DataBindings.Add
   ("Text", dtabProducts, strPKDesc);
textCategoryName.DataBindings.Add
   ("Text", dtabProducts, strFKDesc);

When the user taps on an entry in the combo box, that row is displayed in the text boxes. If the user enters new values in the text boxes, those values are updated to the data set as soon as the user selects a new row; and the validation events for the text box fire before the row is updated. What more could we want?

When writing the code to do data binding, set the DisplayMember property prior to setting the DataSource property. Setting the DataSource property causes things to happen, some of them visible to the user. Therefore, you want everything in place before setting the DataSource property. To illustrate this, you can take the sample code shown on the previous page and reposition the two calls so that the DataSource property is set prior to the DisplayMember property. When you run the code you may notice a slight flicker in the combo box.

Indexing

Indexing is easy to implement but not as easy as matching binding. And although indexing is easy, it is not obvious. When the user positions the scroll bar to a value of n, the application knows that it needs to make row n the current row of the data table. Unfortunately, data tables do not have a current row property because the concept of a current row is meaningful only within data binding. To maintain a current row context when not data-bound would be wasted overhead.

Because data binding involves controls and because controls reside within forms, the Form class assumes the responsibility for managing the bindings and for maintaining the concept of the current row. When the first binding in the previous code excerpt that involved the dtabProducts data table executed, the form created an instance of the BindingContext class for managing dtabProducts bindings. As the subsequent binding code executed, that object was updated with information about the additional bindings.

When the user tapped on the nth entry in the combo box, the form reacted to the combo box’s SelectedIndexChanged event by noting that the combo box was bound to dtabProducts and that the text boxes were also bound to dtabProducts. So, the form notified the text boxes to obtain their Text property values from row n of the data table, and it updated the binding context object for dtabProducts to have a Position property value of n.

To control the current row of a bound data table and thus control which row is displayed in the text boxes, you need to tap into the form’s binding management capability. Specifically, you need to obtain the CurrencyManager object for the data table (or data view) and set its Position property’s value to n. The CurrencyManager object for a data object is contained in the default collection within a form’s BindingContext object. In our application, this means handling the scroll bar’s ValueChanged event as shown in the following line of code (assuming the scroll bar is named hsbRows and its minimum and maximum values are 0 and the number of rows in the data object minus 1, respectively):

this.BindingContext[dtabProducts].Position = hsbRows.Value;

This causes the text boxes to update their Text property values with the values from the row of the dtabProducts table indicated by hsbRows.Value.

It seems like we are saying that indexing implies assigning controls according to the current row. Instead, you might wonder what’s wrong with binding the text boxes to dtabProducts.DefaultView, rather than to the data table itself, and then reacting to the scroll bar’s ValueChanged event by setting the view’s Filter property with the primary key of the nth row, as shown here:

dtabProducts.DefaultView.RowFilter =
         "ProductID = " +
         dtabProducts.Rows[hsbRows.Value]["ProductID"];

Now the text boxes are bound to a view that contains only one row, which is the row they display, and the concept of current row becomes meaningless.

You can do it this way, and it works fine until you try to update the fields by entering new values into the text boxes. When you enter data into the text boxes and then reposition the scroll bar thumb, two things go wrong. First, tapping in the scroll bar does not cause the text box to lose focus, and thus the validation events do not fire. Second, you do not move from one row to another row within the bound view; instead, you replace the contents of the bound view with new contents, and thus the values in the underlying data table are not updated.

So, although indexing does not necessarily imply assigning controls by current row, we recommend doing it that way whenever you choose to use indexing. And as you just saw, you can implement that choice in one line of code.

Search Key

The third method to use when designating the desired row has the user enter a search value, which fits nicely with the indexing option because both are value-based rather than position-based techniques. The single-item controls can be bound to the data table’s default view, as shown in the following code:

//  Bind the DataTable's columns to the text boxes.
textProductID.DataBindings.Add
   ("Text", dtabProducts, strPKName);
textProductName.DataBindings.Add
   ("Text", dtabProducts, strPKDesc);
textCategoryName.DataBindings.Add
   ("Text", dtabProducts, strFKDesc);

Then the controls can be positioned to the requested row by extracting the user-entered key value and specifying it when setting the default view’s Filter property:

dtabProducts.DefaultView.RowFilter =
   "ProductID = " + textGet.Text;

Because ProductID is the primary key, this limits the view to, at most, one row.

Using data views to assign the row to the text boxes in this situation does not lead to the data set updating problems that option had when used in conjunction with a scroll bar. When the user begins to enter a new key value after entering new field values in the old row, the focus does shift, the validation events are called, and the underlying data table is updated.

Conversely, assigning rows according to the current row, which is position-based rather than value-based, does not fit well with having users designate rows by search keys. No single property or method of the DataTable, DataView, or DataRow class translates a primary key value into a row number. If you need this type of translation, you need to write your own routine to provide it.

2.4. Assigning the Controls to a Row

Because, as we have just seen, this subject is so tightly tied to the issue of designating the row to be displayed, we have already covered it. We summarize with these points.

  • If the user is designating the desired row by selecting from a multi-item control bound to the same data object as the single-item controls, do nothing.

  • If the user is designating the desired row by entering an index value, bind your single-item controls to the data table, obtain the binding context for the data table from the form, and set the Position property to the index value.

  • If the user is designating the desired row by entering a search value, bind your single-item controls to the data table’s default view, and use the Filter property to accept only rows that contain the search value.

2.5. Updating the Bound Data Table

We said earlier that data binding is a two-way street: Changes made in the control propagate back to the data set. However, bound controls do not update the underlying row until they are repositioned to a new row. Normally, you want this behavior because the row is probably out of sync with itself as its individual fields are being entered and updated, and it is best to wait for the automatic update that occurs when the user moves to a new row.

It is always possible for the user to complete the editing of a row and not move to a new row. If the user positions to a row, makes a change to the contents through a bound control, and then closes the form, the change the user made is not persisted—it is lost. This is because the application never repositioned to a new row after the change was made, and therefore the data table was not modified.

This is why forms tend to have specific Update and Cancel buttons on them and why we handle not only the Click events of those buttons but also the form’s Closing and Deactivate events. To programmatically complete or cancel an update (i.e., to force or prevent the transfer of data from the single-item controls to the data table), use the CurrencyManager object’s EndCurrentEdit or CancelCurrentEdit method, respectively. For instance, the following code reacts to the form’s Closing event by completing the edit of the current row, thus causing the data in the controls to propagate to the row:

using System.ComponentModel;
   :
   :
   private void FormUpdate_Closing(object sender,
                                   CancelEventArgs e)
   {
      //  Force the current modification to complete.
      this.BindingContext[dtabCategories].EndCurrentEdit ();
   }

The CurrencyManager object has both an EndCurrentEdit method to force the update to occur and a CancelCurrentEdit method to prevent the update from occurring, as well as the Position property that is used to specify the current row.

So, the key points to consider when using data binding to update data set data is how to let the user position the controls to a specific row and how to prevent lost or unintended updates to that row.

This concludes our discussion of moving data between the data set and the presentation layer.

Other  
  •  .NET Compact Framework 3.5 : Examining ADO.NET
  •  Using SQL Server 2005 Integration Services : Programming Integration Services (part 4) - Connecting the Source and Destination Adapters with a Path
  •  Using SQL Server 2005 Integration Services : Programming Integration Services (part 3) - Setting Up Column Information
  •  Using SQL Server 2005 Integration Services : Programming Integration Services (part 2)
  •  Using SQL Server 2005 Integration Services : Programming Integration Services (part 1) - Creating Packages Programmatically - Data Flow
  •  Using SQL Server 2005 Integration Services : Working with Integration Services Packages (part 2) - Data Flow
  •  Using SQL Server 2005 Integration Services : Working with Integration Services Packages (part 1) - Control Flow
  •  SQL Server 2005 : Extending Your Database System with Data Mining - Data Mining Applied (part 2)
  •  SQL Server 2005 : Extending Your Database System with Data Mining - Data Mining Applied (part 1)
  •  # Oracle Coherence 3.5 : Achieving Performance, Scalability, and Availability Objectives (part 2)
  •  
    video
     
    Video tutorials
    - How To Install Windows 8

    - How To Install Windows Server 2012

    - How To Install Windows Server 2012 On VirtualBox

    - How To Disable Windows 8 Metro UI

    - How To Install Windows Store Apps From Windows 8 Classic Desktop

    - How To Disable Windows Update in Windows 8

    - How To Disable Windows 8 Metro UI

    - How To Add Widgets To Windows 8 Lock Screen

    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010
    programming4us programming4us
    programming4us
     
     
    programming4us