ADO Data Binding Windows Form
ADO Data Binding Windows Form
Applied ADO.NET:
Building Data-Driven
Solutions
MAHESH CHAND
AND
DAVID TALBOT
*0732_ch00_CMP2 2/24/03 12:35 PM Page ii
All rights reserved. No part of this work may be reproduced or transmitted in any form or by any
means, electronic or mechanical, including photocopying, recording, or by any information
storage or retrieval system, without the prior written permission of the copyright owner and the
publisher.
Trademarked names may appear in this book. Rather than use a trademark symbol with every
occurrence of a trademarked name, we use the names only in an editorial fashion and to the
benefit of the trademark owner, with no intention of infringement of the trademark.
Editorial Directors: Dan Appleman, Gary Cornell, Jason Gilmore, Simon Hayes, Karen Watterson,
John Zukowski
Distributed to the book trade in the United States by Springer-Verlag New York, Inc., 175 Fifth
Avenue, New York, NY, 10010 and outside the United States by Springer-Verlag GmbH & Co. KG,
Tiergartenstr. 17, 69112 Heidelberg, Germany.
Outside the United States, fax +49 6221 345229, email [email protected], or visit
http://www.springer.de.
For information on translations, please contact Apress directly at 2560 9th Street, Suite 219,
Berkeley, CA 94710. Phone 510-549-5930, fax: 510-549-5939, email [email protected], or visit
http://www.apress.com.
The information in this book is distributed on an “as is” basis, without warranty. Although every
precaution has been taken in the preparation of this work, neither the author nor Apress shall
have any liability to any person or entity with respect to any loss or damage caused or alleged to
be caused directly or indirectly by the information contained in this work.
The source code for this book is available to readers at http://www.apress.com in the Downloads
section.
*0732_ch07_CMP2 2/15/03 8:04 PM Page 287
C HAPTER 7
Data Binding
and Windows Forms
Data-Bound Controls
WHEN IT COMES to developing interactive database applications, it’s difficult to
resist using data-bound controls. Data-bound controls are easy to use, and they
also provide many handy, built-in features. You used DataGrid, ListBox, and other
data-bound controls in the previous chapters. In this chapter, we discuss the
basics of data binding, how to use data-bound controls, and how to develop inter-
active database applications using these controls with a minimal amount of time
and effort.
Both Windows Forms and Web Forms provide a rich set of data-bound con-
trols, which help developers build data-driven Windows and Web applications. In
this chapter, we concentrate on Windows Forms. Chapter 16 covers data binding
in Web Forms.
So what are data-bound controls? You’ve already seen the DataGrid and ListBox
controls in the previous chapters. You used these controls to display data from a
data source. Data-bound controls are Windows controls that represent and
manipulate data in Graphical User Interface (GUI) forms. Both Windows Forms
and Web Forms provide a variety of flexible and powerful data-bound controls.
These data-bound controls vary from a TextBox to a DataGrid.
The process of binding a data source’s data to GUI controls is called data
binding. Most of the editable Windows controls provide data binding, either
directly or indirectly. These controls contain members that connect directly to a
data source, and then the control takes care of displaying the data and other
details. For example, to view data in a DataGrid control, you just need to set its
DataSource property to a data source. This data source could be a DataSet, DataView,
array, collection, or other data source. Data-bound controls can display data, and
287
*0732_ch07_CMP2 2/15/03 8:04 PM Page 288
Chapter 7
they are smart enough to display properties (metadata) of the stored data such as
data relations.
You can divide data binding into two categories: simple data binding and
complex data binding. In simple data binding, a control displays data provided by
a data feed. In fact, the control itself is not capable of displaying complex data.
Setting the Text property of a TextBox or Label control is an example of simple data
binding. Complex data binding, on the other hand, allows controls to bind to mul-
tiple columns and complex data. Binding an entire database table or multiple
columns of a database table to a DataGrid or a ListBox control is an example of
complex data binding.
The Binding class constructor, which creates an instance of the Binding class, takes
three arguments: a data-bound control’s property name, a data source as an
Object, and a data member, usually the name of the data source columns as a
string. You define the Binding class constructor as follows:
288
*0732_ch07_CMP2 2/15/03 8:04 PM Page 289
TextBox1.DataBindings.Add(bind1)
Besides the previous two controls, you can perform simple binding on many
controls including Button, CheckBox, CheckedListBox, ComboBox, DateTimePicker,
DomainUpDown, GroupBox, HscrollBar, Label, LinkLabel, ListBox, ListView, MonthCalender,
NumericUpDown, PictureBox, ProgressBar, RadioButton, RichTextBox, ScrollBar, StatusBar,
TextBox, TreeView, and VscrollBar. Listing 7-2 binds the Text property of a ComboBox,
Label, and Button control with the DataTable’s LastName, City, and Country columns
(respectively).
ComboBox1.DataBindings.Add _
(New Binding("Text", ds, "Employees.LastName"))
TextBox2.DataBindings.Add _
(New Binding("Text", ds, "Employees.EmployeeID"))
Label4.DataBindings.Add(
New Binding("Text", ds, "Employees.City"))
Button1.DataBindings.Add(
New Binding("Text", ds, "Employees.Country"))
289
*0732_ch07_CMP2 2/15/03 8:04 PM Page 290
Chapter 7
290
*0732_ch07_CMP2 2/15/03 8:04 PM Page 291
The Control and DataSource properties return the control and data source that
belong to this binding. The IsBinding property returns True if the binding is active;
otherwise it returns False. PropertyName returns the name of the bound control’s
property that can be used in data binding. Listing 7-5 displays the DataSource and
PropertyName properties of a TextBox.
If (TextBox1.DataBindings(0).IsBinding) Then
Dim ds As DataSet = _
CType(TextBox1.DataBindings(0).DataSource, DataSet)
str = "DataSource : " + ds.Tables(0).TableName
str += ", Property Name: " + _
TextBox1.DataBindings(0).PropertyName
MessageBox.Show(str)
End If
In addition to the previously discussed properties, the Binding class also pro-
vides two protected methods: OnParse and OnFormat. OnParse raises the Parse event,
and OnFormat raises the Format event. The Parse event occurs when the value of a
data-bound control is changing, and the Format event occurs when the property of
a control is bound to a data value. The event handler for both the Parse and Format
events receives an argument of type ConvertEventArgs containing data related to
this event, which has two members: DesiredType and Value. DesiredType returns
the data type of the desired value, and Value gets and sets the value of the
ConvertEventArgs object.
Now let’s say you want to convert text and decimal values for a Binding for a
TextBox. You write the code in Listing 7-6, where you change the Binding type and
add event handlers for the Binding objects for Format and Parse members.
291
*0732_ch07_CMP2 2/15/03 8:04 PM Page 292
Chapter 7
NOTE This listing uses the Customers table instead of Employees because
the Employees table doesn’t have any decimal data. If you want to use the
Employees table, you could convert a Date type to a String type.
NOTE To test this code, you need to create a DataSet from the Employees
table of the Northwind database and use it as a data source when
constructing a Binding object. Also, don’t forget to add a reference to the
System.Globalization namespace because the NumberStyle enumeration
is defined in this namespace.
292
*0732_ch07_CMP2 2/15/03 8:04 PM Page 293
The BindingManagerBase class is an abstract base class. You use its functionality
through its two derived classes: CurrencyManager and PropertyManager.
By default data-bound controls provide neither data synchronization nor the
position of the current item. The BindingManagerBase object provides the data syn-
chronization in Windows Forms and makes sure that all controls on a form are
updated with the correct data.
OK, now let’s say a form has three controls: a TextBox, a Label, and a
PictureBox. All three controls support data binding from a DataSet, which is filled
with the data from the Employees table. The TextBox control displays FirstName,
the Label control displays LastName, and the PictureBox control displays Photo
properties (columns) of the DataSet. All of the controls must be synchronized in
order to display the correct first name, last name, and photo for the same
employee.
CurrencyManager accomplishes this synchronization by maintaining a
pointer to the current item for the list. All controls are bound to the current item
so they display the information for the same row. When the current item changes,
CurrencyManager notifies all the bound controls so that they can refresh their data.
Furthermore, you can set the Position property to specify the row in the DataSet or
DataTable to which the controls point. Figure 7-1 shows the synchronization process.
293
*0732_ch07_CMP2 2/15/03 8:04 PM Page 294
Chapter 7
As you learned, the Binding property returns the collection of binding objects as
a BindingsCollection object that BindingManagerBase manages. Listing 7-7 creates a
BindingManagerBase object for the form and reads all of the binding controls.
NOTE To read a form’s controls that are participating in data binding, you
must make sure that the form’s data source and control’s data source are
the same.
The Count property returns the total number of rows being managed by
BindingManagerBase. The Current property returns the current object, and the
Position property represents (both gets and sets) the position in the underlying
list to which controls bound to this data source point. We use these properties in
the following sample examples.
294
*0732_ch07_CMP2 2/15/03 8:04 PM Page 295
METHOD DESCRIPTION
AddNew Adds a new item to the list
CancelCurrentEdit Cancels the current edit operation
EndCurrentEdit Ends the current edit operation
GetItemProperties Returns the list of property descriptions for the data source
RemoveAt Deletes the row at the specified index
ResumeBinding Resumes data binding
SuspendBinding Suspends data binding
GetListName Protected. Returns the name of the list
OnCurrentChanged Raises the CurrentChanged event, which occurs when the bound
value changes
PullData Pulls data from the data-bound control into the data source
PushData Pushes data from data source into the data-bound control
UpdateIsBinding Updates the binding
CurrencyManager manages a list of Binding objects on a form. It’s inherited from the
BindingManagerBase class. Besides the functionality provided by BindingManagerBase,
the CurrencyManager provides two members: a List property and a Refresh method.
The List property returns the list of bindings maintained by CurrencyManager as an
IList object. To convert an IList to other objects, you need to cast it with the type
of the object, which must implement IList. Some of the objects that implement
IList are DataView, DataTable, DataSet, Array, ArrayList, and CollectionBase.
You create a CurrencyManager object by using the BindingContext object, which
returns either CurrencyManager or PropertyManager, depending on the value of the
data source and data members passed to the Item property of BindingContext. If
the data source is an object that can only return a single property (instead of a list
of objects), the type will be PropertyManager. For example, if you specify a TextBox
295
*0732_ch07_CMP2 2/15/03 8:04 PM Page 296
Chapter 7
control as the data source, PropertyManager will be returned. If the data source is an
object that implements IList, IListSource, or IBindingList, such as a DataSet,
DataTable, DataView, or an Array, CurrencyManager will be returned.
You can create a CurrencyManager from objects such as a DataView and vice
versa. For example, the following code creates a CurrencyManager from a DataView
and a DataView from a CurrencyManager:
Dim dv As DataView
dv = New DataView(ds.Tables("Customers"))
Dim curManager1 As CurrencyManager = DataGrid1.BindingContext(dv)
Dim list As IList = curManager1.List
Dim dv1 As DataView = CType(curManager1.List, DataView)
Dim curManager2 As CurrencyManager = Me.BindingContext(ds1)
Understanding BindingContext
Each object inherited from the Control class has a BindingContext object attached
to it. BindingContext manages the collection of BindingManagerBase objects for
that object such as a form. The BindingContext creates the CurrencyManager and
PropertyManager objects, which were discussed previously. Normally you use the
Form class’s BindingContext to create a CurrencyManager and PropertyManager for a
form and its controls, which provide data synchronization.
The Item property of BindingContext returns the BindingManagerBase (either
CurrencyManager or PropertyManager). The Contains methods returns True if it con-
tains the specified BindingManagerBase.
Besides the Item and Contains members, the BindingContext has three pro-
tected methods: Add, Clear, and Remove. The Add method adds a BindingManagerBase
to the collection, the Clear method removes all items in the collection, and the
Remove method deletes the BindingManagerBase associated with the specified data
source.
Now let’s see data binding in action. In this section, you’ll develop an application
that provides data synchronization. In this application, you’ll build a record navi-
gation system. The controls will display records, and then when you click the
296
*0732_ch07_CMP2 2/15/03 8:04 PM Page 297
Move Next, Move Last, Move Previous, and Move First buttons, the controls will
display the respective records.
To begin, create a Windows application and design a form that looks like
Figure 7-2. For this example, you don’t have to place the ReadBindingMemberInfo
and Remove controls. Add a ComboBox control, two TextBox controls, a ListBox
control, some Label controls, and some Button controls. The Load Data button
loads data to the controls and attaches Binding objects to the BindingContext. You
should also add four buttons with brackets as the text (<<, <, >, >>), which repre-
sents the Move First, Move Previous, Move Next, and Move Last records.
NOTE You can create your own form, but to save you some time, you
can download the code from the Apress (www.apress.com) or C# Corner
(www.c-sharpcorner.com) Web sites. Open the project in Visual Studio
.NET (VS .NET) to understand it better.
As usual, first you add some variables to the project, which shown in Listing 7-8.
Don’t forget to change your server name; the server name in this example is MCB.
297
*0732_ch07_CMP2 2/15/03 8:04 PM Page 298
Chapter 7
Second, you call the LoadData method on the Load Data button click:
Listing 7-9 shows the LoadData and GetDataSet methods. The GetDataSet
method returns a DataSet object from the table name passed in the method. The
LoadData method creates bindings for these controls with different DataTable
columns.
298
*0732_ch07_CMP2 2/15/03 8:04 PM Page 299
The previously discussed steps will load the first row from the Employees
table to the controls. Now, the next step is to write code for the move buttons.
Listing 7-10 shows the code for all four buttons—Move First, Move Next, Move
Previous, and Move Last. As you can see, this code uses the Position and Count
properties of BindingManagerBase to set the position of the new record. Binding-
Context and other Binding objects manage everything for you under the hood.
Listing 7-10. Move Next, Move Previous, Move First, and Move Last Button Code
299
*0732_ch07_CMP2 2/15/03 8:04 PM Page 300
Chapter 7
When you run your application, the first record looks like Figure 7-3. Clicking
the Move First, Move Next, Move Previous, and Move Last buttons will navigate
you through the first, next, previous, and last records of the table (respectively).
Question: When I click the move buttons, I don’t see the pointer in the ListBox
moving. Why not?
Answer: The ListBox control doesn’t use the same binding method as simple
data-bound controls such as TextBox or Label controls. We discuss this in the
following section.
You just saw how to implement a record navigation system using simple data
binding and simple data-bound controls. In the following section, we show you
how to build a record navigation system using complex data-bound controls such
as ListBox and DataGrid controls.
300
*0732_ch07_CMP2 2/15/03 8:04 PM Page 301
Unlike simple data-bound controls, the complex data-bound controls can display
a set of data such as a single column or a collection of columns. The controls such
as DataGrid are even able to display data from multiple tables of a database.
Whereas the simple data-bound controls usually use their Text properties for
binding, complex data-bound controls use their DataSource and DataMember prop-
erties.
In the following sections, we discuss some of the common complex data-
bound controls such as the ComboBox, ListBox, and DataGrid.
The Control class is the mother of all Windows controls. This class’s basic function-
ality is required by visual Windows controls that are available from the Toolbox or
through other wizards. The Control class handles user input through the keyboard,
the mouse, and other pointing devices. It also defines the position and size of con-
trols; however, it doesn’t implement painting.
If you count the Control class members, you’ll find that this class is one of the
biggest classes available in the .NET Framework Library. In the following sections,
you’ll explore some of the data-binding functionality implemented by this class.
The Control class provides two important and useful properties, which play a
vital role in the data-binding process. These properties are BindingContext and
DataBinding. The BindingContext property represents the BindingContext attached
to a control. As discussed earlier, BindingContext returns a single BindingManagerBase
object for all data-bound controls. The BindingManagerBase object provides the
synchronization for all data-bound controls. The Control class also implements
the DataSourceChanged event, which raises when the data source of a control is
changed. We discuss this event in more detail shortly.
The ListControl class is the base class for ListBox and ComboBox controls and imple-
ments the data-binding functionality. The ListControl class provides four
data-binding properties: DataManager, DataSource, DisplayMember, and ValueMember.
The DataManager (read-only) property returns the CurrencyManager object asso-
ciated with a ListControl class.
The DataSource property (both get and set) represents the data source for a
ListControl class.
301
*0732_ch07_CMP2 2/15/03 8:04 PM Page 302
Chapter 7
The DisplayMember (both get and set) represents a string, which specifies the
property of a data source whose contents you want to display. For example, if you
want to display the data of a DataTable’s Name column in a ListBox control, you set
DisplayMember ="Name".
The ValueMember (both get and set) property represents a string, which spec-
ifies the property of the data source from which to draw the value. The default
value of this property is an empty string ("").
You’ll see how to use these properties in the following samples.
302
*0732_ch07_CMP2 2/15/03 8:04 PM Page 303
To raise these events, just change the value of the ListBox properties (see
Listing 7-12).
303
*0732_ch07_CMP2 2/15/03 8:04 PM Page 304
Chapter 7
Now you'll learn how to use complex data binding in a ComboBox and a ListBox
control. Unlike simple data-bound controls, complex data-bound controls
maintain the default binding synchronization. For instance, if you bind a data
source with a ListBox and a ComboBox control, and then move from one item to
another in a control, you can see the selection change in the second control
respective to the item you select in the first control.
To prove this theory, you’ll create a Windows application with a ComboBox and
three ListBox controls. The final form looks like Figure 7-4.
As usual, you load data in the Form_Load event. Listing 7-13 shows the event
handler code, where you call the FillDataSet and BindListControl methods.
304
*0732_ch07_CMP2 2/15/03 8:04 PM Page 305
The FillDataSet method simply opens the connection and fills data in a
DataSet. Listing 7-14 shows this method.
The BindListControls method is where you bind the ComboBox and ListBox con-
trols. Listing 7-15 shows the BindListControls method. As you can see, this code
binds the ComboBox to the EmployeeID column and binds the three ListBox controls
to the FirstName, LastName, and Title columns.
If you run the application and select any record in the ComboBox or ListBox con-
trols, you’ll see that the other controls select the correct value. For instance, if you
select the sixth record in the ComboBox, all of the ListBox controls reflect this choice
(see Figure 7-5).
305
*0732_ch07_CMP2 2/15/03 8:04 PM Page 306
Chapter 7
The DataGrid control is much more powerful than any other data-bound control.
It’s also capable of displaying data relations. A DataGrid control displays data in a
tabular, scrollable, and editable grid. Like other data-bound controls, a DataGrid
control can display data from various sources with the help of its DataSource
property. The DataSource property can be a DataTable, DataView, DataSet, or
DataViewManager.
When a DataSet or a DataViewManager contains data from more than one table,
you can specify what table you want to display in the DataGrid property by using
the DataMember property. For example, let’s say you have a DataSet that contains two
tables—Customers and Orders. By default, if you bind a DataSet, it’ll display data
from both tables. But if you want to display data from the Customers table only,
you need to set the DataMember property to the table’s name. Listing 7-16 sets the
DataSource and DataMember properties of a DataGrid.
306
*0732_ch07_CMP2 2/15/03 8:04 PM Page 307
Listing 7-16. Setting the DataSource and DataMember Properties of a DataGrid Control
ds = New DataSet()
sql = "SELECT * FROM Customers"
ds = New DataSet()
adapter = New SqlDataAdapter(sql, conn)
adapter.Fill(ds)
DataGrid1.DataSource = ds
DataGrid1.DataMember = "Customers"
You can also set the DataSource and DataMember properties by using the
DataGrid control’s SetDataBinding method. This method takes the first argument as
a dataSource and the second argument as a dataMember. Typically, a data source is a
DataSet, and the dataMember is the name of a table available in the DataSet. The fol-
lowing code shows how to call the SetDataBinding method of a DataGrid control:
DataGrid1.SetDataBinding(ds, "Customers")
You’ll use the DataGrid control and its members throughout this chapter.
Removing data binding from a data-bound control is simple. The following code
snippet deletes data binding from a DataGrid control:
DataGrid1.DataSource = null;
DataGrid1.DataMember = "";
The DataGrid control is one of the most flexible and versatile controls in Windows
Forms. In this section, we discuss some of the DataGrid functionality.
The DataGrid class represents the DataGrid control in Windows Forms. Before
writing any code, you’ll learn about the DataGrid class properties and methods.
Figure 7-6 shows a DataGrid’s parent items and background, and Figure 7-7 shows
some of the DataGrid parts.
307
*0732_ch07_CMP2 2/15/03 8:04 PM Page 308
Chapter 7
308
*0732_ch07_CMP2 2/15/03 8:04 PM Page 309
Like all other Windows controls, the DataGrid inherits from the Control class, which
means that the data-binding functionality defined in the Control class is available
in the DataGrid control. Besides the hundreds of members implemented in the
Control class, the DataGrid provides many more members. Table 7-2 describes
the DataGrid class properties.
PROPERTY DESCRIPTION
AllowNavigation Indicates whether navigation is allowed. True or false. Both
get and set.
AllowSorting Indicates whether sorting is allowed. True or false. Both get
and set.
AlternatingBackColor Background color of alternative rows.
BackColor Background color of the grid.
BackgroundColor Color of the nonrow area of the grid. This is the background
color if the grid has no rows.
BorderStyle Style of the border.
CaptionBackColor Background color of caption.
CaptionFont Font of caption.
CaptionForeColor Foreground color of caption.
CaptionText Caption text.
CaptionVisible Indicates whether caption is visible.
ColumnHeadersVisible Indicates whether column headers are visible.
CurrentCell Returns current selected cell.
CurrentRowIndex Index of the selected row.
DataMember Represents the data sources among multiple data sources. If
there’s only one data source, such as a DataTable or a DataSet
with a single table, there’s no need to set this property. Both
get and set.
DataSource Represents the data source such as a DataSet, a DataTable, or
Ilist.
FirstVisibleColumn Index of the first visible column.
FlatMode FlatMode. Type of FlatMode enumeration.
ForeColor Foreground color.
GridLineColor Color of grid lines.
309
*0732_ch07_CMP2 2/15/03 8:04 PM Page 310
Chapter 7
PROPERTY DESCRIPTION
GridLineStyle Style of grid lines.
HeaderBackColor Background color of column headers.
HeaderFont Font of column headers.
HeaderForeColor Foreground color of column headers.
Item Value of the specified cell.
LinkColor Color of the text that you can click to navigate to a child table.
LinkHoverColor Link color changes to when the mouse moves over it.
ParentRowBackColor Background color of parent rows. Parent rows are rows that
allow you to move to child tables.
ParentRowForeColor Foreground color of parent rows.
ParentRowLabelStyle Label style of parent rows.
ParentRowsVisible Indicates whether parent rows are visible.
PreferredColumnWidth Default width of columns in pixel.
PreferredRowHeight Default height of rows in pixels.
ReadOnly Indicates whether grid is read only.
RowHeaderVisible Indicates whether row header is visible.
RowHeaderWidth Width of row headers.
SelectionBackColor Background color of selected rows.
SelectionForeColor Foreground color of selected rows.
TableStyles Table style. DataGridTableStyle type.
VisibleColumnCount Total number of visible columns.
VisibleRowCount Total number of visible rows.
HorizScrollBar Protected. Returns the horizontal scroll bar of the grid.
VertScrollBar Protected. Returns the horizontal scroll bar of the grid.
ListManager Protected. Returns the CurrencyManager of the grid.
310
*0732_ch07_CMP2 2/15/03 8:04 PM Page 311
METHOD DESCRIPTION
BeginEdit Starts the editing operation
BeginInit Begins the initialization of grid that is used on a form or
used by other components
Collapse Collapses children if a grid has parent and child
relationship nodes expanded
EndEdit Ends the editing operation
EndInit Ends grid initialization
Expand Expands children if grid has children in a parent/child
relation
GetCurrentCellBounds Returns a rectangle that specifies the four corners of the
selected cell
HitTest Gets information when clicking on the grid
IsExpanded True if node of the specified row is expanded; otherwise
false
IsSelected True if specified row is selected; otherwise false
NavigateBack Navigates to the table previously displayed in the grid
NavigateTo Navigates to the table specified by the row and relation
name
ResetAlternatingBackColor Resets the AlternatingBackColor property to the default
color
ResetBackColor Resets background color to default
ResetGridLineColor Resets grid lines color to default
ResetHeaderBackColor Resets header background to default
ResetHeaderFont Resets header font to default
ResetHeaderForeColor Resets header foreground color to default
ResetLinkColor Resets link color to default
ResetSelectionBackColor Resets selection background color to default
ResetSelectionForeColor Resets selection foreground color to default
Select Selects a specified row
SetDataBinding Sets the DataSource and DataMember properties
UnSelect Unselects a specified row
311
*0732_ch07_CMP2 2/15/03 8:04 PM Page 312
Chapter 7
Besides the methods described in Table 7-3, the DataGrid class provides some
protected methods (see Table 7-4).
The DataGrid class comes with 13 helper objects (classes, structures, and
enumerations). What do we mean by helper classes? Helper classes provide
simple methods to access some of the more complicated aspects of
the DataGrid class. These helper objects are DataGrid.HitTestInfo, the
DataGrid.HitTestType enumeration, DataGridBoolColumn, the DataGridCell
structure, DataGridColumnStyle, DataGridColumnStyle.CompModSwitches, the
DataGridColumnStype.DataGridColumnHeaderAccessibleObject DataGridLineStyle
enumeration, the DataGridParentRowsLabelStyle enumeration,
DataGridPreferredColumnWidthTypeConverter,
312
*0732_ch07_CMP2 2/15/03 8:04 PM Page 313
The DataGrid control hides much more functionality in it. Not only can it display
data and data relations, it also provides functionality to customize its styles
including color, text, caption, and font. The TableStyles property of DataGrid
opens the door for formatting a grid and its columns. The GridStyles property
returns an object of GridTableStyleCollection, which is a collection of
DataGridTableStyle.
DataGridTableStyle represents the style of a DataTable that can be viewed in
the grid area of a DataGrid. The GridTableStyles class of DataGridTableStyle repre-
sents a collection of DataGridColumnStyle. Figure 7-8 represents the relationship
between the DataGrid-related style objects. We discuss these objects in more detail
in the following sections.
Before you see these objects in action, you’ll look at these object classes and
their members briefly.
313
*0732_ch07_CMP2 2/15/03 8:04 PM Page 314
Chapter 7
The DataGridTableStyle object customizes the grid style for each DataTable in a
DataSet. However, the DataGridTableStyle name is a little misleading. From its
name, you would probably think the DataGridTableStyle represents the style of a
DataGridTable such as its color, text, and font. Correct? Actually, DataGridTableStyle
represents the grid itself. Using DataGridTableStyle, you can set the style and
appearance of each DataTable, which is being displayed by the DataGrid. To specify
which DataGridTableStyle is used when displaying data from a particular DataTable,
set the MappingName to the TableName of a DataTable. For example, if a DataTable’s
TableName is Customers, you set MappingName to the following:
DataGridTableStyle.Mapping="Customers"
314
*0732_ch07_CMP2 2/15/03 8:04 PM Page 315
You’ll now learn how to create a DataGridTableStyle object and add it to the
DataGrid’s DataGridTableStyle collection. Listing 7-17 creates a DataGridTableStyle,
set its properties, and adds two columns: a Boolean and a text box column. The
DataGridBookColumn class represents a Boolean column with check boxes, and the
DataGridTextBoxColumn represents a text box column. (We discuss these classes in
the following sections.)
315
*0732_ch07_CMP2 2/15/03 8:04 PM Page 316
Chapter 7
PROPERTY DESCRIPTION
Alignment Alignment of text in a column. Both get and set.
DataGridTableStyle Returns the DataGridTableStyle object associated with the
column.
HeaderText Text of the column header. Both get and set.
MappingName Name used to map the column style to a data member. Both
get and set.
NullText You can set the column text when the column has null
values using this property. Both get and set.
PropertyDescriptor PropertyDescriptor object that determines the attributes of
data displayed by the column. Both get and set.
ReadOnly Indicates if column is read only. Both get and set.
Width Width of the column. Both get and set.
316
*0732_ch07_CMP2 2/15/03 8:04 PM Page 317
Besides the methods described in Table 7-6, the DataGridColumnStyle class pro-
vides a method, ResetHeaderText, which resets the header text to its default value
of null.
METHOD DESCRIPTION
Abort Aborts the edit operation.
BeginUpdate Suspends the painting operation of the column until the
EndUpdate method is called.
CheckValidDataSource If a column is not mapped to a valid property of a data source,
this throws an exception.
ColumnStartEditing Informs DataGrid that the user has start editing the column.
Commit Completes the editing operation.
ConcedeFocus Notifies a column that it must relinquish the focus to the
control it’s hosting.
Edit Prepares the cell for editing a value.
EndUpdate Resumes the painting of columns suspended by calling the
BeginUpdate method.
EnterNullValue Enters a DBNullValue into the column.
GetColumnValueAtRow Returns the value in the specified row.
GetMinimumHeight Returns the minimum height of a row.
GetPreferedHeight Returns the height used for automatically resizing columns.
GetPreferedSize Automatic size.
Invalidate Redraws the column.
SetColumnValueAtRow Sets a value in the specified row.
SetDataGrid Sets the DataGrid to which this column belongs.
SetDataGridInColumn Sets the DataGrid for the column.
UpdateUI Updates the value of a row.
317
*0732_ch07_CMP2 2/15/03 8:04 PM Page 318
Chapter 7
PROPERTY DESCRIPTION
AllowNull Represents whether null values are allowed in this column or not (both
get and set)
FalseValue Represents the actual value of column when the value of column is set to
False (both get and set)
NullValue The actual value used when setting the value of the column to Value
(both get and set)
TrueValue Represents the actual value of column when the value of column is set to
True (both get and set)
318
*0732_ch07_CMP2 2/15/03 8:04 PM Page 319
319
*0732_ch07_CMP2 2/15/03 8:04 PM Page 320
Chapter 7
As mentioned, the DataGrid provides properties to set the foreground and back-
ground color of almost every part of a DataGrid such as headers, grid lines, and so
on. The DataGrid also provides font properties to set the font of the DataGrid.
Listing 7-21 sets Font and Color properties of a DataGrid.
Listing 7-21. Using Some of the DataGrid’s Color and Font Properties
TIP You can customize a DataGrid and allow the user to select a color and
font for each part of the DataGrid at runtime as well as at design-time
using the Properties window.
You just saw the Font property of the DataGrid itself. The DataGrid also provides
properties to set the caption’s fonts and color. For example, Listing 7-22 sets the
font, background color, and foreground color of caption of the DataGrid.
320
*0732_ch07_CMP2 2/15/03 8:04 PM Page 321
321
*0732_ch07_CMP2 2/15/03 8:04 PM Page 322
Chapter 7
322
*0732_ch07_CMP2 2/15/03 8:04 PM Page 323
323
*0732_ch07_CMP2 2/15/03 8:04 PM Page 324
Chapter 7
You can use a hit test to get information about a point where a user clicks a
control. There are many real-world usages of a hit test. For example, say you want
to display two pop-up menus when a user right-clicks a certain area on a DataGrid.
One area is on the DataGrid column header; this right-click pop-up menu will have
options such as Sort Ascending, Sort Descending, Hide, and Find. As you can
pretty guess from these names, the sort menu items will sort a column’s data in
ascending and descending order, the Hide menu item will hide (or delete) a
column, and the Find menu item will search for a keyword in the selected column.
The second pop-up menu will pop up when you right-click any grid’s cell. This
menu will have options such as Move First, Move Previous, Move Next, and Move
Last that will allow you to move to the first, previous, next, and last rows of a
DataGrid.
Now, using only these two cases, you can find out what DataGrid part is pro-
cessing the hit test action (in other words, which one is being clicked by the user).
The HitTest method of DataGrid performs a hit test action.
The HitTest method takes a point and returns the DataGrid.HitTestInfo object,
which determines the part of a DataGrid clicked by the user. It’s useful when you’re
designing a custom grid and want to do different things when user clicks different
parts of the DataGrid.
The DataGrid.HitTestInfo class has three properties: Column, Row, and Type. The
Column and Row properties represent the number of the column and row that the
user has clicked. The Type property specifies the part of the DataGrid that is clicked.
324
*0732_ch07_CMP2 2/15/03 8:04 PM Page 325
MEMBER DESCRIPTION
Caption Returns True if the caption was clicked.
Cell Returns True if a cell was clicked.
ColumnHeader Returns True if a column header was clicked.
ColumnResize Represents the column border, the line between column headers.
None Returns True if the background area was clicked.
ParentRow The parent row displays information about the parent table of the
currently displayed child table.
RowHeader Returns True if the row header was clicked.
RowResize Returns True if the line between rows.
You can also check the Type property against the combination of
DataGrid.HitTestType enumeration members. Listing 7-26 is the mouse down
event handler of a DataGrid, which tracks almost every portion of a DataGrid and
generates a message when you right-click a DataGrid. Simply copy this code, right-
click the DataGrid, and see it in action.
325
*0732_ch07_CMP2 2/15/03 8:04 PM Page 326
Chapter 7
Let’s see this in action. Create a Windows application, add a DataGrid control,
two Button controls, two Label controls, two TextBox controls, and a ListBox
control. Next, set their properties and positions. The final form looks like Figure 7-9.
As you can see, to exchange two columns, you enter column index in both text
boxes and click the Exchange Columns button.
326
*0732_ch07_CMP2 2/15/03 8:04 PM Page 327
Now let’s write the code. As usual, you first define some variables:
Next, add a new method called FillDataGrid, which fills the DataGrid from the
Customers table of the Northwind database. You call the FillDataGrid method
from the form’s Load event handler (see Listing 7-27). You can also see from the
FillDataGrid method, this code adds DataGridTableStyle to each DataTable in a
DataSet.
327
*0732_ch07_CMP2 2/15/03 8:04 PM Page 328
Chapter 7
Now you write code on the Exchange Columns button click event handler (see
Listing 7-28). As you can see, you need to make sure that the text boxes aren’t
empty. After that you call the ReshuffleColumns method, which actually moves the
columns from one position to another.
328
*0732_ch07_CMP2 2/15/03 8:04 PM Page 329
329
*0732_ch07_CMP2 2/15/03 8:04 PM Page 330
Chapter 7
If i = col2 Then
NewTableStyle.GridColumnStyles.Add _
(existingTableStyle.GridColumnStyles(col1))
End If
If i <> col1 And col1 > col2 Then
NewTableStyle.GridColumnStyles.Add _
(existingTableStyle.GridColumnStyles(i))
End If
Next
' Remove the existing table style and add new style
grid.TableStyles.Remove(existingTableStyle)
grid.TableStyles.Add(NewTableStyle)
End Sub
330
*0732_ch07_CMP2 2/15/03 8:04 PM Page 331
Now run the application and enter 1 in the Column 1 box and enter 2 in the
Column 2 box and then click the Exchange Columns buttons. You’ll see both
columns switched their positions. Now if you click the Get Grid Columns and
Tables Info button, the output looks like Figure 7-10.
331
*0732_ch07_CMP2 2/15/03 8:04 PM Page 332
Chapter 7
Listing 7-31 returns the column name when a user right-clicks a DataGrid column
header.
Now you’ll learn a few more uses of a DataGrid control. Hiding a DataGrid column is
simply a job of finding the right column and setting its Width property to 0. For an
example, see the TotalDataGrid sample that comes with the downloads from
www.apress.com.
To make your program look better, you’ll create a right-click pop-up menu, as
shown in Figure 7-11.
332
*0732_ch07_CMP2 2/15/03 8:04 PM Page 333
To create a pop-up menu, you declare a ContextMenu and four MenuItem objects
as sortAscMenu, sortDescMenu, findMenu, and hideMenu. You also define two more
variables to store the current DataGridColumnStyle and column name as follows:
Case DataGrid.HitTestType.ColumnHeader
' Add context menus
popUpMenu = New ContextMenu()
popUpMenu.MenuItems.Add("Sort ASC")
popUpMenu.MenuItems.Add("Sort DESC")
popUpMenu.MenuItems.Add("Find")
popUpMenu.MenuItems.Add("Hide Column")
Me.ContextMenu = popUpMenu
Me.BackColor = Color.Sienna
sortAscMenu = Me.ContextMenu.MenuItems(0)
sortDescMenu = Me.ContextMenu.MenuItems(1)
findMenu = Me.ContextMenu.MenuItems(2)
hideMenu = Me.ContextMenu.MenuItems(3)
' Find the Column header name
Dim gridStyle As DataGridTableStyle = _
dtGrid.TableStyles("Customers")
curColName = gridStyle.GridColumnStyles _
(hti.Column).MappingName.ToString()
curColumnStyle = gridStyle.GridColumnStyles(hti.Column)
333
*0732_ch07_CMP2 2/15/03 8:04 PM Page 334
Chapter 7
Finally, you write the Find menu button click event handler and set
curColumnStyle.Width to 0:
By default a DataGrid provides you with sorting options when you click a DataGrid
column. But there may be occasions when you don’t want to use the default
behavior and instead want to implement your own custom sorting.
In Figure 7-11, you saw the Sort ASC and Sort DESC menu options. As you
probably remember from Chapter 3 and Chapter 4, sorting is easy to implement in
a DataView. To sort a DataView, you simply set the Sort property of the DataView to
the column name and to ASC or DESC for ascending and descending sorting,
respectively. Listing 7-33 shows the Sort ASC and Sort DESC menu event handler
code.
334
*0732_ch07_CMP2 2/15/03 8:04 PM Page 335
Move methods are one of the features of the ADO recordset that don’t appear in
ADO.NET. A recordset provides MoveFirst, MoveNext, MovePrevious, and MoveLast
methods to move to the first, next, previous and last record in a recordset (respec-
tively). In this example, you’ll implement move functionality in a DataGrid control.
Listing 7-34 implements move functionality in a custom recordset class called
CustRecordSet.vb. (We already discussed how you can use BindingContext to move
the current pointer from one position to another.) In this code, CreateRecordSet
simply fills and binds a DataSet to the grid. The FirstRecord, PrevRecord, NextRecord,
and LastRecord methods set the current position of the pointer to the first row,
current row –1, current row +1, and the last row (respectively).
NOTE In this example, the table name is Customers. You may want to
customize the name so it can work for any database table.
Imports System.Data.SqlClient
335
*0732_ch07_CMP2 2/15/03 8:04 PM Page 336
Chapter 7
End Sub
Public Sub LastRecord()
If frm.BindingContext(Me.dataSet, mapName) Is Nothing Then
Return
End If
frm.BindingContext(Me.dataSet, mapName).Position = _
frm.BindingContext(Me.dataSet, mapName).Count - 1
End Sub
End Class
Now create a Windows application and add a DataGrid control and four
Button controls (Move First, Move Next, Move Previous, and Move Last). The
form’s Load event calls FillDataSet, which creates a new CustRecordSet object and
calls its CreateRecordSet method, which in turn fills data in a DataGrid control
and binds a DataSet with the DataGrid control (see Listing 7-35).
336
*0732_ch07_CMP2 2/15/03 8:04 PM Page 337
337
*0732_ch07_CMP2 2/15/03 8:04 PM Page 338
Chapter 7
You just saw how to implement custom sorting in a DataGrid control. After sorting,
searching is one more basic requirement of database-driven applications. There
are two methods to implement search in a DataGrid control:
338
*0732_ch07_CMP2 2/15/03 8:04 PM Page 339
You already used search functionality in a connected environment using the SQL
SELECT statement. Do you remember using a SELECT statement with a WHERE clause?
In a WHERE clause, you passed the criteria specifying the data for which you’re
looking. If you want to search in multiple tables, you construct a JOIN query with
WHERE clause. You can even search for a keyword using the SELECT...LIKE statement,
which was discussed in “The DataView in Connected Environments” section of
Chapter 4.
You use the SELECT statement in a DataAdapter, which reads data based on the
SELECT statement and the criteria passed in it. However, using this method for
searching may not be useful when you’re searching data frequently—especially
when you’re searching data in a DataGrid. We suggest not using this method when
searching data in an isolated application and there’s no other application updating
the data. Why? The main reason is that every time you change the SELECT statement,
you need to create a new DataAdapter and fill the DataSet when you change the
SELECT statement. This method is useful when there are multiple applications
updating the data simultaneously and you want to search in the latest updated
data.
The DataTable and DataView objects provide members that can filter data based on
criteria. (See “The DataView” section in Chapter 3 for more information.) You can
simply create a DataView from a DataSet, set a DataView’s RowFilter property to the
search criteria, and then bind the DataView to a DataGrid, which will display the fil-
tered records.
TIP Using the same method, you can implement a Search or Find feature
in a DataGrid control. You can also provide a Search option on a DataGrid
control’s header so that you know on which column a user has clicked.
The new application looks like Figure 7-13. Obviously, the Search button
searches the column entered in the Column Name text box for a value entered in
the Value text box.
339
*0732_ch07_CMP2 2/15/03 8:04 PM Page 340
Chapter 7
NOTE If you search for a string, use a singe quote (') before and after the
string.
After creating a Windows application and adding controls to the form, define
following variables:
340
*0732_ch07_CMP2 2/15/03 8:04 PM Page 341
Now add the FillDataGrid method, which fills the DataGrid and creates a
DataView called searchView (see Listing 7-37).
Now, the next step is to set a RowFilter of searchView based on the values
entered in the Column Name and Value text fields. Listing 7-38 shows the code for
the Search button. As you can see, the code sets the RowFilter of searchView and
binds it to the DataGrid to display the filtered data.
341
*0732_ch07_CMP2 2/15/03 8:04 PM Page 342
Chapter 7
At this time, if you run the application, the data from the Orders table is filled
in the DataGrid. If you enter EmployeeID in the Column Name text box and 6 in the
Value field and then click the Search button, the filtered data looks like Figure 7-14.
As you learned earlier, the DataGrid control is one of the most powerful, flexible,
and versatile controls available in Windows Forms. It has an almost unlimited
number of properties and methods. You can add new records, update records, and
delete existing records on a DataGrid with little effort, and you can easily save the
affected data in a database.
342
*0732_ch07_CMP2 2/15/03 8:04 PM Page 343
When a DataGrid control is in edit mode (the default mode), you can simply
add a new record by clicking the last row of the grid and editing the column
values. You can update data by changing the existing value of cells. You can delete
a row by simply selecting a row and clicking the Delete button.
In the previous example, you used a Save Changes button on a form (see
Figure 7-14). Now just write the code in Listing 7-39 on the Save Changes button
click to save the data.
Listing 7-39. Saving Updated Data in a Data Source from a DataGrid Control
343
*0732_ch07_CMP2 2/15/03 8:04 PM Page 344
Chapter 7
End If
End If
ds.AcceptChanges()
DataGrid1.Refresh()
End Sub
As you can see from Listing 7-39, you simply get the modified, deleted, and
updated changes in a new DataSet by calling the DataSet.GetChanges method and
save the changes by calling the DataAdapter.Update method. In the end, you accept
the changes by calling DataSet.AcceptChanges and refresh the DataGrid control by
calling the DataGrid.Refresh method.
Summary
Data-bound controls are definitely one of the greatest additions to GUI applica-
tions. In this chapter, we discussed the basics of data binding and how data
binding works in Windows Forms data-bound controls and ADO.NET. We dis-
cussed some essential objects that participate in the data-binding phenomena,
including Binding, BindingContext, BindingsCollection, BindingManagerBase,
PropertyManager, CurrencyManager, and BindingContext.
After discussing basics of data binding and how to use these objects, you
learned about some data-bound controls and how to bind data using the data-
binding mechanism. You also saw some practical usage of data binding and
data-bound controls; specifically, you created a record navigation system with a
DataGrid control. Some of the examples discussed in this chapter included
changing DataGrid styles programmatically, binding data sources to various data-
bound controls, building a record navigation application, and implementing
search, add, update, and delete record features in a DataGrid.
The next chapter covers constraints and data relations in more detail.
344