Bind data to the user interface.
Data binding refers to the process of creating a link between a data model and the user interface of an application. The data model can be any source of data within the application: It might be an array of values, an Extensible Markup Language (XML) file, or data stored in a database. The user interface consists of the controls that are contained on the forms in an application.
The .NET Framework includes extremely flexible data binding capabilities. In the following sections you'll learn about many of those capabilities, including the following:
Complex data binding
One-way and two-way data binding
The BindingContext object
The Data Form Wizard
Simple data binding means connecting a single value from the data model to a single property of a control. For example, you might bind the Vendor object name from a list of vendors to the Text property of a TextBox control.
Looking at the code in Step by Step 5.1, you can see that the .NET Framework supplies an object model for binding. The control has a ControlBindingsCollection (accessed through its DataBindings property), which contains instances of the Binding class. By default there are no Binding objects in the collection, so when you create a control, it's not bound. To bind the control, you can add a new Binding object to the ControlBindingsCollection by using its Add() method.
Because the Add() method creates a new instance of the Binding class, it takes the same parameters as the constructor for that class:
The name of the property to bind to
The data source to bind
The navigation path to the particular data; in this particular example, the navigation path is empty because there's only one thing to bind to in this array
Now that you've seen simple data binding in action, it's time to explore the topic in a bit more depth. I'll start by looking at which entities can be bound to the user interface. Then I'll talk about which properties you can bind to those entities.
Finally, I'll explain the architecture that the .NET Framework uses to manage simple data binding. You'll see that you can work directly with the objects that handle the data binding connections if you need to interact with the data binding process from code.
In Step by Step 5.1, the Text property of the TextBox control is bound to an element from an array. The Binding class can accept many other types of data sources, including the following:
An instance of any class that implements the IBindingList or ITypedList interface, including the DataSet, DataTable, DataView, and DataViewManager classes.
An instance of any class that implements the IList interface on an indexed collection of objects. In particular, this applies to classes that inherit from System.Array, including C# arrays.
An instance of any class that implements the IList interface on an indexed collection of strongly typed objects. For example, you can bind to an array of Vendor objects.
Binding to a collection of strongly typed objects is a convenient way to handle data from an object-oriented data model.
STEP BY STEP5.2 Using Simple Data Binding with a Strongly Typed IList
|
At this point, you could be forgiven for thinking that binding makes only the first element of a data source available on the user interface. In fact, bound controls are designed to let the user move through an entire collection of data. Later in this chapter you'll learn the details of the BindingContext object, which enables you to manipulate the data behind a bound control. But as a preview, Figure 5.1 shows a form that lets you scroll through the data in a strongly typed IList.
STEP BY STEP5.3 Scrolling Through Data, Using the BindingContext Object
|
Just as the .NET Framework allows flexibility in the source of bound data, it allows flexibility on the user interface side of the equation. You can use simple data binding with any control that has a DataBindings property, which includes any control that is derived from System.Windows.Forms.Control. In practice, that means almost any control you can drop on a Windows form.
You can bind just about any property of these bindable controls to an item of data. This gives you enormous flexibility in building a user interface that depends on data. For example, you can bind an array of DateTime values to the Value property of a DateTimePicker control.
STEP BY STEP5.4 Binding Data to a DateTimePicker Control
|
If you think creatively, you can find many ways to use simple data binding beyond simply displaying text in a text box. Here are some possibilities:
Display a set of photos by binding them to the Image property of a PictureBox control.
Show the relative magnitude of quantities with the Value property of a ProgressBar control.
Color-code an area of a form by binding to the BackColor property of a Panel control.
When you use simple data binding to connect a control to a data source, the .NET Framework creates a pair of objects to manage the binding: a CurrencyManager object and a BindingContext object. Depending on the number of controls on the form and the data to which they are bound, a single form might involve several of each of these objects.
The CurrencyManager object is responsible for keeping track of which piece of data from a data source is currently bound to the user interface. Although so far you've only seen one data bound control on each form, a single form can contain multiple bound controls. If all the controls are bound to the same data source, they can share a CurrencyManager object. But a single form can involve multiple CurrencyManager objects as well. Suppose, for example, that you built a form with an array of Vendor objects bound to one control and an array of DateTime objects bound to another control. In that case, the form would have two CurrencyManager objects.
Any bound form will also have at least one BindingContext object. The job of the BindingContext object is to keep track of the various CurrencyManager objects on the form. The indexer of the BindingContext object returns a CurrencyManager object. Consider this line of code:
this.BindingContext[adtBound].Position += 1;
That tells the BindingContext object for the form to return a CurrencyManager object for the adtBound data source and then to increment the Position property of the CurrencyManager object. The CurrencyManager object encapsulates the knowledge of how to move the pointer within the data array when the Position property is changed.
Like forms, container controls (such as the GroupBox, Panel, or TabControl controls) can have their own BindingContext objects. By using these separate BindingContext objects, you can create forms that have independently scolling views of the same data.
You'll learn about the CurrencyManager object in more depth later in this chapter. But first, let's look at another variety of data binding: complex data binding.
REVIEW BREAK
|
In complex data binding, you bind a user interface control to an entire collection of data, rather than to a single data item. A good example of complex data binding involves the DataGrid control. Figure 5.2 shows a bound DataGrid control displaying data from the Suppliers table in the SQL Server 2000 Northwind database.
EXAM TIP
Database Terminology In some literature you'll see the rows of a database table referred to as records or tuples and the columns referred to as fields or attributes.
You'll learn how to build this form later in the chapter, but for now, just concentrate on its features. DataGrid is a single control that displays many pieces of data. In this case, the data is taken from the rows and columns of the Suppliers table in a SQL Server 2000 database. You can click any cell in the DataGrid control and edit the data that the cell contains. If the form is properly programmed, these edits are reflected in the underlying data.
NOTE
The Northwind Sample Database Whenever I've used data from a database in this book, I've used the Northwind sample database that comes as part of SQL Server 2000. Visual Studio .NET includes Microsoft Data Engine (MSDE), a stripped-down version of SQL Server that you can use if you don't have the full version installed. See your Visual Studio CD's readme file for information on installing MSDE. You can also find the Northwind sample database in any version of Microsoft Access, but the Access version does not ship with Visual Studio .NET. The code in this book assumes that you are using the SQL Server version.
Obviously, complex data binding is a powerful tool for transferring large amounts of data from a data model to a user interface. The following sections dig into the mechanics of complex data binding with two examples:
Binding to a ComboBox or ListBox control
Binding to a DataGrid control
The ComboBox and ListBox controls both provide ways for the user to select one item from a list of data. The difference between the two is that in the ListBox control, the list is visible at all times, whereas in the ComboBox control, the list is hidden until the user clicks the drop-down arrow at the end of the box. Either of these controls can be loaded with an entire list of data via complex data binding.
STEP BY STEP5.5 Binding Data to a List in a ListBox Control
|
Step by Step 5.5 works by first creating an array of exam objects that contains all the information you'd like to display in the list portion of a ListBox control. It then sets the DataSource property of the ListBox control to the name of the array and the DisplayMember property to the name of the object property that supplies the text for the list. The result is a ListBox control that allows the user to choose from a list of exam names.
As it stands, Step by Step 5.5 doesn't do anything with the data after the user chooses an item from the list. But frequently you'll use a complex data-bound ListBox or ComboBox control in conjunction with a simple data-bound control such as a TextBox control. By selecting a row in the ListBox control, the user can choose a value for the TextBox control.
You can think of the ListBox control in this case as a little pump that moves data from one part of the data model to another. Step by Step 5.6 shows how you can set this up.
STEP BY STEP5.6 Using a ListBox Control with Two Data Bindings
|
Understanding Step by Step 5.6 is crucial for the effective use of ComboBox and ListBox controls in applications. Using these controls can be a little tricky because the ListBox control is bound to two different things. Here's a review of how it all fits together:
The DataGrid control provides a way to display many rows from a data model at one time. The DataGrid control is designed to let you see an entire collection of data (often called a resultset) at one time.
STEP BY STEP5.7 Binding an Array of Objects to a DataGrid Control
|
The DataGrid control is a mainstay of data display for Visual C# .NET forms. As such, it is extremely configurable. Visual Studio .NET includes two interfaces for setting the display propeties of a DataGrid control. First, you can set individual properties to control the look of the DataGrid control in the Properties window. Second, you can use AutoFormats to quickly apply a whole new look to a DataGrid control.
STEP BY STEP5.8 Applying an AutoFormat to a DataGrid Control
|
When you need more precise formatting for a DataGrid control than the Auto Format dialog box allows, or when you just don't care for the look of any of the AutoFormats, you can set individual display properties for the DataGrid control. Table 5.1 lists the properties you can use to control the look of the DataGrid control.
Property | Description |
---|---|
AlternatingBackColor | Specifies the background color to use for even-numbered rows in the grid. |
BackColor | Specifies the background color to use for odd-numbered rows in the grid. |
BackgroundColor | Specifies the color to use for any portion of the control that's not filled with data. |
BorderStyle | Offers the choices None, FixedSingle, and Fixed3D for the borders of the control. |
CaptionBackColor | Specifies the background color for the caption portion of the control. |
CaptionFont | Specifies the font for the caption portion of the control. |
CaptionText | Specifies the text to display in the caption portion of the control. |
CaptionVisible | Controls whether a caption will be displayed. This is a Boolean property. |
ColumnHeadersVisible | Controls whether each column will have a header. This is a Boolean property. |
FlatMode | Controls whether the grid will have a 3D or a flat appearance. This is a Boolean property. |
Font | Specifies the font for text in the control. |
ForeColor | Specifies the foreground color for text in the control. |
GridlineColor | Specifies the color for the lines of the grid. |
GridlineStyle | Offers the choices None and Solid. |
HeaderBackColor | Specifies the background color for column and row headers. |
HeaderFont | Specifies the font for column and row headers. |
HeaderForeColor | Specifies the foreground color for column and row headers. |
LinkColor | Specifies the color to use for hyperlinks between sections of a control. |
ParentRowBackColor | Specifies the background color to use for the parent rows area. |
ParentRowsForeColor | Specifies the text color to use for the parent rows area. |
ParentRowsLabelStyle | Offers the choices None, TableName, ColumnName, and Both. |
ParentRowsVisible | Controls whether the parent rows area will be visible. This is a Boolean property. |
PreferredColumnWidth | Specifies the default width for columns, in pixels. |
PreferredRowHeight | Specifies the Default height for rows, in pixels. |
RowHeadersVisible | Controls whether each row will have a header. This is a Boolean property. |
RowHeaderWidth | Specifies the default width of row headers, in pixels. |
SelectionBackColor | Specifies the background color for any selected cells. |
SelectionForeColor | Specifies the text color for any selected cells. |
Figure 5.5 shows a complex-data-bound DataGrid control that displays data from several database tables at one time, to help you understand where each of the areas mentioned in Table 5.1 is located. You'll learn how to bind database tables to the DataGrid control later in this chapter.
GUIDED PRACTICE EXERCISE 5.1In this exercise, you'll be working with employee data from Skylark Spaceways. This exercise helps you review the basic syntax of both simple and complex data binding, as well as the use of the DataGrid control. Table 5.2 shows the data that you need to manage.
You need to display this data in two different ways. First, you should create a form that displays information about one employee at a time, with buttons to scroll through the list. To save space, the form should show the employee names and positions in TextBox controls. However, it should also make the home planet information available as a ToolTip on the employee names text box. There should be a button on this form to open a second form. The second form should display the entire employee roster in grid form. How would you create such a form? You should try working through this problem on your own first. If you get stuck, or if you'd like to see one possible solution, follow these steps:
If you have difficulty following this exercise, review the sections “Simple Data Binding” and “Complex Data Binding,” earlier in this chapter. The text and examples should help you relearn this material and help you understand what happens in this exercise. After doing that review, try this exercise again. |
REVIEW BREAK
|
Data binding in Windows forms can be one-way or two-way. In one-way data binding, the bound property of the control reflects changes to the data model, but changes to the control are not written back to the data model. For example, if you display a list of customers in a ComboBox control, the act of selecting a customer from the ComboBox control does not do anything to modify the list.
In two-way data binding, changes to the control are written back to the data model. For example, if you have a TextBox control that is bound to the CustomerName property of a Customer object, changing the text in the TextBox control changes the corresponding property of the object.
NOTE
Using the DataAdapter Object to Update a Data Source To transmit changes from a data model back to the original data source, you usually use the Update() method of the DataAdapter object. For more detailed information on the DataAdapter object, see Chapter 6 and the section “Using the Data Form Wizard,” later in this chapter.
Simple data binding on Windows forms is automatically two-way. Any changes you make to the data on the form are automatically transmitted back to the data model. However, it's important to note that the data model might not be the ultimate data source. The most common exception to this occurs when you use the ADO.NET classes to access data from a database and place it in a data model. Changes to bound data on a form are written back to the data model, but they are not automatically returned to the database (though you can write code to do this).
You saw the BindingContext and CurrencyManager classes earlier in the chapter, in the discussion of the overall architecture of data binding. Now that you've seen both simple and complex data binding in action, it's time to look at the BindingContext and CurrencyManager classes in somewhat more detail.
The BindingContext class exists primarily as a means to retrieve the CurrencyManager objects on a form. Table 5.3 shows the important interface members of the BindingContext class. In many applications, you can let Visual C# .NET manage these objects for you. But there are times when it's useful to work directly with these objects. For instance, the CurrencyManager class provides event hooks that let you react to modifications that the user makes to bound data.
Member | Type | Description |
---|---|---|
Contains() | Method | Indicates whether the BindingContext object contains a specific BindingManagerBase object |
The BindingManagerBase class is an abstract class that is implemented in both the CurrencyManager class and the PropertyManager class. You've already seen the CurrencyManager class. The PropertyManager class is used to manipulate the current value of an individual property, rather than the property of the current object in a list; you are unlikely to have any reason to use the PropertyManager class yourself. Table 5.4 shows the important interface members of the BindingManagerBase class.
Member | Type | Description |
---|---|---|
AddNew() | Method | Adds a new object to the underlying list |
Bindings | Property | Gets the collection of bindings being managed by the class |
CancelCurrentEdit() | Method | Cancels any edits that are in progress |
Count | Property | Gets the number of rows managed by the class |
Current | Property | Gets the current object |
CurrentChanged | Event | Occurs when the bound value changes |
EndCurrentEdit() | Method | Commits any edits that are in progress |
GetItemProperties() | Method | Gets a list of item properties for the current object in the list |
Position | Property | Gets or sets the position in the underlying list that is bound with this class |
PositionChanged | Event | Occurs when the Position property changes |
RemoveAt() | Method | Removes the object at the specified position from the underlying list |
ResumeBinding() | Method | Resumes data binding |
SuspendBinding() | Method | Suspends data binding |
The CurrencyManager class implements most of the interfaces of the BindingManagerBase class, and it includes a few others of its own. Table 5.5 lists the important interface members of the CurrencyManager class.
Member | Type | Description |
---|---|---|
AddNew() | Method | Adds a new object to the underlying list |
Bindings | Property | Gets the collection of bindings being managed by the class |
CancelCurrentEdit() | Method | Cancels any edits that are in progress |
Count | Property | Gets the number of rows managed by the class |
Current | Property | Gets the current object |
CurrentChanged | Event | Occurs when the bound value changes |
EndCurrentEdit() | Method | Commits any edits that are in progress |
GetItemProperties() | Method | Gets a list of item properties for the current object in the list |
ItemChanged | Event | Occurs when the current item has been altered |
List | Property | Gets the IList interface from the data source |
Position | Property | Gets or sets the position in the underlying list that is bound with the class |
PositionChanged | Event | Occurs when the Position property changes |
Refresh() | Method | Repopulates the bound controls |
RemoveAt() | Method | Removes the object at the specified position from the underlying list |
ResumeBinding() | Method | Resumes data binding |
SuspendBinding() | Method | Suspends data binding |
EXAM TIP
Understanding the Binding Objects You don't need to memorize every detail of the BindingContext, BindingManagerBase, and CurrencyManager objects. Instead, concentrate on knowing the overall uses of these objects. The BindingContext class is your hook to retrieve the CurrencyManager object. The BindingManagerBase class supplies the Position property that lets you see where you are in a set of bound data. The CurrencyManager class supplies the event hooks that let you interact with user-initiated data changes.
In addition to manipulating the Position property, you'll likely find the CurrencyManager class most useful for managing events on data-bound forms. Of course, because this class doesn't have a visual, control-based representation, you need to set up these events in code. Listing 5.1 shows how you can create delegates and respond to the events of the CurrencyManager class.
// Create an array of vendor objects Vendor[] aVendors = new Vendor[3]; private void Listing5_1_Load(object sender, System.EventArgs e) { // Initialize the vendors array aVendors[0] = new Vendor("Microsoft"); aVendors[1] = new Vendor("Rational"); aVendors[2] = new Vendor("Premia"); // Bind the array to the text box txtVendorName.DataBindings.Add( "Text", aVendors, "VendorName"); CurrencyManager cm = (CurrencyManager) this.BindingContext[aVendors]; cm.CurrentChanged += new EventHandler( CurrencyManager_CurrentChanged); cm.ItemChanged += new ItemChangedEventHandler( CurrencyManager_ItemChanged); cm.PositionChanged += new EventHandler( CurrencyManager_PositionChanged); } private void CurrencyManager_CurrentChanged(Object o, EventArgs e) { lbEvents.Items.Add("CurrentChanged"); } private void CurrencyManager_ItemChanged(Object o, System.Windows.Forms.ItemChangedEventArgs icea) { lbEvents.Items.Add("ItemChanged"); } private void CurrencyManager_PositionChanged(Object o, System.EventArgs ea) { lbEvents.Items.Add("PositionChanged"); } private void btnPrevious_Click(object sender, System.EventArgs e) { // Move to the previous item in the data source this.BindingContext[aVendors].Position -= 1; } private void btnNext_Click(object sender, System.EventArgs e) { // Move to the previous item in the data source this.BindingContext[aVendors].Position += 1; } |
EXAM TIP
An Event Mnemonic You can remember that the CurrentChanged event refers to the user interface changes by noting that current and control begin with the same letter.
The names of the CurrencyManager class events may be a bit confusing. The ItemChanged event is fired when the data itself is changed by an external factor. For example, if you modify the data in an array and that array is bound to the user interface, the ItemChanged event occurs. The CurrentChanged event occurs when the data is changed on the user interface. This is true whether the user changes the data by typing a control or the CurrencyManager changes the data by responding to a change of the Position property. Finally, the PositionChanged event occurs when the Position property is changed. In practice, you see a CurrentChanged event whenever you see a PositionChanged event, but you can also get CurrentChanged events without a change in the Position property.
STEP BY STEP5.9 Using CurrencyManager Events
|
If you compare the code in Step by Step 5.9 with the code from Guided Practice Exercise 5.1, you see that using the PositionChanged event can save you from writing duplicate code in every procedure where you might change the Position property of the CurrencyManager class.
Now that you've seen the mechanics of data binding, it's time to explore one of the tools that Visual C# .NET offers for automatic data binding: the Data Form Wizard. In the following sections, you'll see how to use the wizard to build both a single-table form and a multiple-table form. This helps you ease into the broad topic of using data from databases, which occupies the rest of this chapter and all of Chapter 6.
IN THE FIELD: A CRASH COURSE IN DATABASESAlthough this exam doesn't have any objectives that explicitly demand database knowledge, you can't pass the exam without knowing something about databases. These days, databases are part of the pervasive understructure of computing. You're expected to understand the basics of them, just as you understand the basics of files and folders. At this point you're interested in data stored in relational databases. A relational database (such as Microsoft SQL Server, which is used in the examples in this book) stores data in tables, each of which represents an instance of a particular entity. An entity is anything that you're interested in tracking in the database: a customer, an order, an employee, or a supplier, for example. A single database can contain many tables; for example, you might have tables named Customers, Orders, Employees, and Suppliers. Each table contains one row (or record) for each instance of an entity: If you have 50 customers, then the Customers table should have 50 rows. Each row consists of a number of columns (or fields) that describe the entity. For example, these might be the fields in a Customers table:
In a well-designed database, each entity can be identified by a column or combination of columns called the primary key. For example, the primary key for the Customers table could be the Customer Number column. Each customer would then have a unique and unchanging customer number. If you knew the customer number, you could use it to look up all the other information that the database stores about that customer. SQL Server is called a relational database because it accommodates the fact that there are relationships between entities that are stored in different tables. Think about customers and orders, for example. Each order is placed by a single customer. You can indicate this by storing the customer number (the primary key of the Customers table) in the Orders table. These might be the columns of the Orders table:
In this case, the Order Number column would be the primary key (the unique identifying column) of the Orders table. The Customer Number column in the Orders table serves to relate each order to a corresponding row in the Customers table. You call the Customer Number a foreign key in the Orders table. To specify a relationship between tables, you name the two tables and the columns that match between them. Relationships can be one-to-many (one customer can place many orders) or one-to-one (one employee has at most one pension). Databases can contain other objects in addition to tables. These include views (which can provide a subset of information from one or more tables) and stored procedures (which are groups of SQL statements that are compiled into execution plans) and users and groups (which control the security of database objects). NOTE Database Design For more information on relational database design and terminology, refer to Que's SQL Server 2000 Programming by Carlos Rojas and Fernando Guerrero. If you've never worked with a database, you may find it a bit confusing at first. But if you work through the examples carefully, it should become clear to you. |
You'll start by building a data form that displays data from a single table: the Customers table in the Northwind sample database.
STEP BY STEP5.10 Building a Single-Table Data Form
|
Figure 5.13 shows the finished Data Form created in Step by Step 5.10. It contains 10 buttons, which have the following functions:
NOTE
Data Form Wizard–Generated Code May Not Be Optimal The Data Form Wizard is designed to create output for a variety of combinations of forms (Windows forms, Web forms, and so on), controls, and data sources. Therefore, the code generated by the wizard might not be the optimal code for any one specific situation. As you progress through this book, you will learn techniques for optimizing the data access code.
You might want to browse through the code that the wizard created behind this form. Be warned, though, that there are more than 600 lines of code involved in implementing this functionality! Obviously, the Data Form Wizard can save you a lot of time in building data-bound forms.
As you continue through the book, you'll learn more about database objects and the code that you can use to manipulate them. For now, you'll stick to the relatively easy user interface tools as you explore what can be done with data binding.
You can use the Data Form Wizard to build a form that displays data from more than one table. Step by Step 5.11 explains how to do this.
STEP BY STEP5.11 Building a Multiple-Table Data Form
|
As you select different Customer table rows in the upper DataGrid control on the form created in Step by Step 5.11, the lower DataGrid control changes to show only the Order rows for that customer. Figure 5.15 shows an example.
Once again, you'll find a tremendous amount of code (about 500 lines) behind this form. And once again, you'll leave it for future inspection.
REVIEW BREAK
|
18.222.48.60