Binding Data .
575
inserting new items
You now have a sample application that enables you to browse and make changes to an existing set
of individual customers. The one missing piece is the capability to create a new customer. By default,
the Add button on the BindingNavigator is automatically wired up to the AddNew method on the
BindingSource, as shown earlier in this chapter. In this case, you actually need to set some default
values on the record that is created in the Customer table. To do this, you need to write your own
logic behind the Add button.
The first step is to remove the automatic wiring by setting the AddNewItem property of the
CustomerBindingNavigator to (None), otherwise, you will end up with two records being created
every time you click the Add button. Next, double-click the Add button to create an event handler
for it. You can then modify the default event handler as follows to set initial values for the new
customer, as well as create records in the other two tables:
Vb
Private Sub BindingNavigatorAddNewItem_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) _
Handles BindingNavigatorAddNewItem.Click
Dim drv As DataRowView
'Create record in the Customer table
drv = TryCast(Me.CustomerBindingSource.AddNew, DataRowView)
Dim customer = TryCast(drv.Row, AdventureWorksLTDataSet.CustomerRow)
customer.rowguid = Guid.NewGuid
customer.PasswordHash = String.Empty
customer.PasswordSalt = String.Empty
customer.ModifiedDate = Now
customer.FirstName = "<first name>"
customer.LastName = "<last name>"
customer.NameStyle = False
Me.CustomerBindingSource.EndEdit()
End Sub
c#
private void bindingNavigatorAddNewItem_Click(object sender, EventArgs e){
DataRowView drv;
//Create record in the Customer table
drv = this.customerBindingSource.AddNew() as DataRowView;
var customer = drv.Row as AdventureWorksLTDataSet.CustomerRow;
customer.rowguid = Guid.NewGuid();
customer.PasswordHash = String.Empty;
customer.PasswordSalt = String.Empty;
customer.ModifiedDate = DateTime.Now;
customer.FirstName = "<first name>";
customer.LastName = "<last name>";
customer.NameStyle = false;
this.customerBindingSource.EndEdit();
}
576 .
chaPter 27 dATASeTS And dATAbinding
From this example, it seems that you are unnecessarily setting some of the properties — for
example, PasswordSalt and PasswordHash being equal to an empty string. This is necessary to
ensure that the new row meets the constraints established by the database. Because these fields
cannot be set by the user, you need to ensure that they are initially set to a value that can be
accepted by the database. Clearly, for a secure application, the PasswordSalt and PasswordHash
would be set to appropriate values.
Running the application with this method instead of the automatically wired event handler enables
you to create a new Customer record using the Add button. If you enter values for each of the
fields, you can save the changes.
Validation
In the previous section, you added functionality to create a new customer record. If you
don’t enter appropriate data upon creating a new record — for example, if you don’t enter a
first name — this record will be rejected when you click the Save button. The schema for the
AdventureWorksDataSet contains a number of constraints, such as FirstName can’t be null, which
are checked when you perform certain actions, such as saving or moving between records. If these
checks fail, an exception is raised. You have two options. One, you can trap these exceptions,
which is poor programming practice, because exceptions should not be used for execution control.
Alternatively, you can preempt this by validating the data prior to the schema being checked.
Earlier in the chapter, when you learned how the BindingNavigator automatically wires the
AddNew method on the BindingSource, you saw that the OnAddNew method contains a call to a
Validate method. This method propagates up and calls the Validate method on the active control,
which returns a Boolean value that determines whether the action will proceed. This pattern is used
by all the automatically wired events and should be used in the event handlers you write for the
navigation buttons.
The Validate method on the active control triggers two events — Validating and Validated —
that occur before and after the validation process, respectively. Because you want to control the
validation process, add an event handler for the Validating event. For example, you could add an
event handler for the Validating event of the FirstNameTextBox control:
Vb
Private Sub FirstNameTextBox_Validating(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.CancelEventArgs) _
Handles FirstNameTextBox.Validating
Dim firstNameTxt As TextBox = TryCast(sender, TextBox)
If firstNameTxt Is Nothing Then Return
e.Cancel = (firstNameTxt.Text = String.Empty)
End Sub
c#
private void firstNameTextBox_Validating(object sender, CancelEventArgs e){
var firstNameTxt = sender as TextBox;
if (firstNameTxt == null) return;
e.Cancel = (firstNameTxt.Text == String.Empty);
}
Binding Data .
577
Though this prevents users from leaving the
textbox until a value has been added, it doesn’t
give them any idea why the application prevents
them from proceeding. Luckily, the .NET
Framework includes an ErrorProvider control
that can be dragged onto the form from the
Toolbox. This control behaves in a manner similar
to the tooltip control. For each control on the
form, you can specify an Error string, which,
when set, causes an icon to appear alongside
the relevant control, with a suitable tooltip
displaying the Error string. This is illustrated
in Figure 27-14, where the Error string is set
for the FirstNameTextBox.
Clearly, you want only to set the Error string
property for the FirstNameTextBox when there is
no text. Following from the earlier example in which you added the event handler for the Validating
event, you can modify this code to include setting the Error string:
Vb
Private Sub FirstNameTextBox_Validating(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.CancelEventArgs) _
Handles FirstNameTextBox.Validating
Dim firstNameTxt As TextBox = TryCast(sender, TextBox)
If firstNameTxt Is Nothing Then Return
e.Cancel = (firstNameTxt.Text = String.Empty)
If String.IsNullOrWhiteSpace(firstNameTxt.Text) Then
Me.ErrorProvider1.SetError(firstNameTxt, "First Name must be specified")
Else
Me.ErrorProvider1.SetError(firstNameTxt, Nothing)
End If
End Sub
c#
private void firstNameTextBox_Validating(object sender, CancelEventArgs e){
var firstNameTxt = sender as TextBox;
if (firstNameTxt == null) return;
e.Cancel = (firstNameTxt.Text == String.Empty);
if (String.IsNullOrEmpty(firstNameTxt.Text)){
this.errorProvider1.SetError(firstNameTxt, "First Name must be specified");
}
else{
this.errorProvider1.SetError(firstNameTxt, null);
}
}
You can imagine that having to write event handlers that validate and set the error information for each
of the controls can be quite a lengthy process. Rather than having individual validation event handlers for
fiGure 27-14
578 .
chaPter 27 dATASeTS And dATAbinding
each control, you may want to rationalize them into a single event handler that delegates the validation to
a controller class. This helps ensure your business logic isn’t intermingled within your user interface code.
customized datasets
At the moment, you have a form that displays some basic information about a customer. However,
it is missing some of her address information, namely her Main Office and/or Shipping addresses.
If you look at the structure of the AdventureWorksLT database you will notice that there is a manyto-
many relationship between the Customer and Address tables, through the CustomerAddress
linking table. The CustomerAddress has a column AddressType that indicates the type of address.
While this structure supports the concept that multiple Customers may have the same address, the
user interface you have built so far is only interested in the address information for a particular
customer. If you simply add all three of these tables to your DataSet you will not easily be able to
use data binding to wire up the user interface. As such it is worth customizing the generated DataSet
to merge the CustomerAddress and Address tables into a single entity.
Open up the DataSet designer by double-clicking the AdventureWorksLTDataSet.xsd in the
Solution Explorer. Select the AddressTableAdapter, which you should already have from earlier in
the chapter, expand out the SelectCommand property in the Properties tool window, and then click the
ellipses next to the CommandText property. This will again open up the Query Builder. Currently, you
should only have the Address table in the diagram pane. Right-click in that pane, select Add Table,
and then select the CustomerAddress table. Check all fields in the CustomerAddress table except
AddressID and then go to the Criteria pane and change the Alias for the rowguid and ModifiedDate
columns coming from the CustomerAddress table. The result should look similar to Figure 27-15.
fiGure 27-15
Binding Data .
579
When you click the OK button you will be prompted to regenerate the Update and Insert
statements. The code generator can’t handle multiple table updates so will fail regardless of which
option you select. This means that you need to manually define the update, insert, and delete
statements. You can do this by defining stored procedures within the AdventureWorksLT database
and then to update the CommandType and CommandText for the relevant commands in the
AddressTableAdapter as shown in Figure 27-16.
Now that your DataSet contains both Customer and Address DataTables, the only thing missing
is the relationship connecting them. As you have customized the Address DataTable the designer
hasn’t been able to automatically create the relationship. To create a relation, right-click anywhere
on the DataSet design surface and select Add . Relation. This opens the Relation dialog as shown
in Figure 27-17.
fiGure 27-16 fiGure 27-17
In accordance with the way the Address DataTable has been created by combining the
CustomerAddress and Address tables, make the Customer DataTable the parent and the Address the
child. When you accept this dialog you will see a relationship line connecting the two DataTables on
the DataSet design surface.
bindingsource chains and the dataGridView
After completing the setup of the DataSet with the Customer and Address DataTables you are ready
to data bind the Address table to your user interface. So far you’ve been working with simple input
controls such as textboxes, drop-down lists, and labels, and you’ve seen how the BindingNavigator
enables you to scroll through a list of items. Sometimes it is more convenient to display a list of
580 .
chaPter 27 dATASeTS And dATAbinding
items in a grid. This is where the DataGridView is useful, because it enables you to combine the
power of the BindingSource with a grid layout.
In this example, you extend the Customer Management interface by adding address information
using a DataGridView. Returning to the Data Sources window, select the Address node from under
the Customer node. From the drop-down list, select DataGridView and drag the node into an empty
area on the form. This adds the appropriate BindingSource and TableAdapter to the form, as well as
a DataGridView showing each of the columns in the Address table, as shown in Figure 27-18.
fiGure 27-18
If you recall from earlier, the CustomerBindingSource has the AdventureWorksLTDataSet as
its DataSource, with the Customer table set as the DataMember. This means that controls
that are data bound using the CustomerBindingSource are binding to a field in the Customer
table. If you look at the AddressBindingSource you will see that its DataSource is actually the
CustomerBindingSource, with its DataMember set to Customer_Address, which is the relationship
you created between the two DataTables. As you would expect, any control being data bound using
the AddressBindingSource is binding to a field in the Address table. However, the difference is that
unlike the CustomerBindingSource, which returns all Customers, the AddressBindingSource is only
populated with the Addresses that are associated with the currently selected Customer.
Unlike working with the Details layout, when you drag the DataGridView onto the form it ignores
any settings you might have specified for the individual columns. Instead, every column is added to
Working with Data sources .
581
the grid as a simple text field. To modify
the list of columns that are displayed, you
can either use the smart tag for the newly
added DataGridView or select Edit Columns
from the right-click context menu. This
will open the Edit Columns dialog
(shown in Figure 27-19), in which columns
can be added, removed, and reordered.
After specifying the appropriate columns, the
finished application can be run, and the list of
orders will be visible for each customer in the
database.
fiGure 27-19
workinG with data
sources
In this chapter you have been working with a strongly typed DataSet that contains a number of
rows from the Customer table, based on a Title parameter. So far the example has only had one
tier, which is the Windows Forms application itself. In this section you see how you can use Visual
Studio 2010 to build a multi-tier application.
Start by creating two new projects, CustomerBrowser (Windows Forms Application) and
CustomerService (ASP.NET Web Service Application). Change the Application Type of the initial
project to Class Library by double-clicking the Properties node in Solution Explorer and then