Encapsulation with Data Controls
With this exhaustive look at data binding, you now know how to tailor data for your
This state of affairs is far from ideal. Luckily, there are a few ways to minimize the problem. One way is not to use data binding at all. Instead, create a database table with three
public class DBHelper { public static void FillForm(Form formToBind, DataTable mappings) { DataRow[] rowMatch(); foreach (Control ctrl in formToBind.Controls) { // See if this menu item has a corresponding row. rowMatch = mappings.Select("ControlName = "' + ctrl.Text + ""'); // If it does, configure the binding
This technique works well because it establishes an extra layer of indirection between the database and the controls. It's easy to modify this table if field names or user interface elements change. Best of all, the routine to fill the user interface is quite generic. Of course, you need to manually call this method every time the user moves to a new row to ensure that control synchronization occurs as naturally as it does with data binding.
Another way to help separate your database from your user interface code is by keeping database-specific content like field names and constants (used in the Parse and Format
Validating Bound Data
Earlier in this chapter, you learned that one problem with ADO.NET data binding is validation. You can write specific error-handling code for each control, which is often a good approach, but one that creates extra code and ends up importing database details into your form code. Another approach is to handle the DataTable events like ColumnChanging, ColumnChanged, RowChanging, and RowChanged. The potential problem here is that the user may browse to another record, not
Taking control of data binding navigation allows you to provide a more elegant solution. First, you create two
private int currentPage; private bool errFlag; You also need to hook up the events for column changes and position changes.
storeBinding.PositionChanged += new EventHandler(Binding_PositionChanged); dsStore.Tables["Products"].ColumnChanged += new DataColumnChangeEventHandler(TableChanging); Next, you make the record navigation conditional on the current record being valid. If the ErrFlag member variable is set to true, the user is automatically sent back to the original page.
private void Binding_PositionChanged(object sender, System.EventArgs e) { if (errFlag) { // Reset the page. storeBinding.Position = currentPage; } else { // Allow the page to change and update the currentPage variable. currentPage = storeBinding.Position; } }
Next, you add the validation code, which occurs in response to a table change. This event is
private void TableChanging(object sender, System.Data.DataColumnChangeEventArgs e) { string errors = DBStore.ValidateProduct(e.Row); if (errors == "") { errFlag = false; } else { errFlag = true; } lblErrorSummary.Text = errors; }
You'll notice that so far this form doesn't contain any database-specific code. Instead, the validation is performed by passing the current row to a special static method provided by a database class. This method returns an error string, or an empty string if the validation succeeded.
public class DBStore { public static string ValidateProduct(DataRow row) { string errors = ""; if (((decimal)row["UnitCost"]) <= 0) { errors += "* UnitCost value too low\n"; } if (row["ModelNumber"].ToString() == "") { errors += "* You must specify a ModelNumber\n"; } if (row["ModelName"].ToString() == "") { errors += "* You must specify a ModelName\n"; } return errors; } }
The error message is displayed in the window. Everything works
Figure 9-21 shows the final application detecting an error.
|
||||||||||||||||||||||||||||||||||||