Data Grid View
Data Grid View
6
Presenting Data with the
DataGridView Control
217
Noyes.book Page 218 Thursday, December 15, 2005 3:57 PM
DataGridView Overview
The DataGridView control is a very powerful, flexible, and yet easy-to-use
control for presenting tabular data. It is far more capable than the Data-
Grid control and is easier to customize and interact with. You can let the
grid do all the work of presenting data in tabular form by setting the data-
binding properties on the control appropriately. You can also take explicit
control of presenting data in the grid through the new features of unbound
columns and virtual mode. Unbound columns let you formulate the con-
tents of the cell as the cells are being added to the grid. Virtual mode gives
you a higher degree of control by allowing you to wait until a cell is actu-
ally being displayed to provide the value it will contain.
You can make the grid act like a spreadsheet, so that the focus for inter-
action and presentation is at the cell level instead of at the row or column
level. You can control the formatting and layout of the grid with fine-
grained precision simply by setting a few properties on the control. Finally,
you can plug in a number of predefined column and cell control types, or
provide your own custom controls, and you can even mix different control
types within different cells in the same row or column.
Figure 6.1 shows an example of a DataGridView control in action with
some of the key visual features highlighted. You can see that the grid picks
up the visual styles of Windows XP; they are much like many of the
Windows Forms controls in .NET 2.0. The grid is composed of columns
and rows, and the intersection of a column and a row is a cell. The cell is
the basic unit of presentation within the grid, and is highly customizable in
New Row Indicator Edit Indicator Combo Box Column CheckBox Column Button Column
adapter.Fill(customers);
// Bind binding source to the data set
m_CustomersBinding source.DataSource = customers;
// Bind grid to the Customers table within the data source
m_Grid.DataSource = m_CustomersBinding source;
m_Grid.DataMember = "Customers";
}
Any time the DataSource and/or DataMember properties are set, the
grid will iterate through the items found in the data collection and will
refresh the data-bound columns of the grid. If the grid is bound to a bind-
ing source, any change to the underlying data source to which the binding
source is bound also results in the data-bound columns in the grid being
refreshed. This happens because of events raised from the binding source
to any bound controls whenever its underlying collection changes.
Like most properties on the DataGridView control, any time the Data-
Source and DataMember properties are set, they fire the DataSource-
Changed and DataMemberChanged events, respectively. This lets you hook
up code that responds to the data binding that has changed on the grid. You
can also react to the DataBindingComplete event, since that will fire after
the data source or data member has changed and data binding has been
updated. However, if you are trying to monitor changes in the data source,
you usually are better off monitoring the corresponding events on the
BindingSource component rather than subscribing to the events on the
grid itself. This is especially true if the code you are using to handle the
event affects other controls on the form. Because you should always bind
your controls to a binding source instead of the data source itself if possible,
the binding source is the best place to monitor changes in the data source.
accept new values for unbound rows or for virtual mode, as described later
in this chapter.
// Do this:
DataGridViewTextBoxColumn newCol = new DataGridViewTextBoxColumn();
// Or this:
DataGridViewTextBoxCell newCell = new DataGridViewTextBoxCell();
DataGridViewColumn newCol2 = new DataGridViewColumn(newCell);
// Then add to the columns collection:
m_Grid.Columns.Add(newCol);
m_Grid.Columns.Add(newCol2);
If you add columns this way, their name and header values are null
by default. To set these or other properties on the columns, you can
access the properties on the column instance before or after adding
it to the Columns collection. You could also index into the Columns
collection to obtain a reference to a column, and then use that refer-
ence to get at any properties you need on the column.
Noyes.book Page 224 Thursday, December 15, 2005 3:57 PM
• Set the grid’s ColumnCount property to some value greater than zero.
This approach is mainly used to quickly create a grid that only con-
tains text box cells or to add more text box columns to an existing grid.
When you set the ColumnCount property like this, the behavior
depends on whether there are already any columns in the grid. If there are
existing columns and you specify fewer than the current number of col-
umns, the ColumnCount property will remove as many columns from the
grid as necessary to create only the number of columns you specify, start-
ing from the rightmost column and moving to the left. This is a little
destructive and scary because it lets you delete columns without explicitly
saying which columns to eliminate, so I recommend to avoid using the
ColumnCount property to remove columns.
However, when adding text box columns, if you specify more columns
than the current number of columns, additional text box columns will be
added to the right of the existing columns to bring the column count up to
the number you specify. This is a compact way to add some columns for
dynamic situations.
// Add a row
m_Grid.Rows.Add();
Several overloads of the Add method let you add multiple rows in a sin-
gle call or pass in a row that has already been created. The DataGridView
control also supports creating heterogeneous columns, meaning that the
Noyes.book Page 225 Thursday, December 15, 2005 3:57 PM
To add a row to a grid this way, the grid must already have been initial-
ized with the default set of columns that it will hold. Additionally, the
number of cells in the row that is added must match that column count. In
this code sample, five text box columns are implicitly added by specifying
a column count of five, then the first row is added as a heterogeneous row
by constructing the cells and adding them to the row before adding the
row to the grid.
You can also save yourself some code by using the grid’s existing col-
umn definitions to create the default set of cells in a row using the Create-
Cells method, then replace just the cells that you want to be different from
the default:
To access the contents of the cells programmatically, you index into the
Rows collection on the grid to obtain a reference to the row, and then index
into the Cells collection on the row to access the cell object.
Once you have a reference to the cell, you can do anything with that cell
that the actual cell type supports. If you want to access specific properties
or methods exposed by the cell type, you have to cast the reference to the
actual cell type. To change a cell’s contents, you set the Value property to
an appropriate value based on the type of the cell. What constitutes an
appropriate value depends on what kind of cell it is. For text box, link, but-
ton, and header cell types, the process is very similar to what was
described for the Binding object in Chapter 4. Basically, the value you set
on the Value property of the cell needs to be convertible to a string, and
formatting will be applied in the painting process. To change the format-
ting of the output string, set the Format property of the style used for the
cell. The style is an instance of a DataGridViewCellStyle object and is
exposed as another property on the cell, not surprisingly named Style.
The cell’s style contains other interesting properties that you can set
(described later in the section “Formatting with Styles”).
For example, if you want to set the contents of a text box cell to the cur-
rent date using the short date format, you could use the following code:
m_Grid.Rows[0].Cells[2].Value = DateTime.Now;
m_Grid.Rows[0].Cells[2].Style.Format = "d";
This sets the value of the third cell in the first row to an instance of a
DateTime object, which is convertible to a string, and sets the format string
to the short date predefined format string “d” (which is the short date for-
mat—MM/YYYY). When that cell gets rendered, it will convert the stored
DateTime value to a string using the format string.
scenarios where you want to present unbound data. You will need to pro-
grammatically create all the columns in the grid (although you can get the
designer to write this code for you too, as shown later), but you can use
events to make populating the values a little easier, especially when you
mix bound data with unbound columns.
An unbound column is a column that isn’t bound to data. You add
unbound columns to a grid programmatically, and you populate the col-
umn’s cells either programmatically as shown in the previous section or by
using events as shown in this section. You can still add columns to the grid
that are automatically bound to columns or properties in the data items of
the data source. You do this by setting the DataPropertyName property
on the column after it is created. Then you can add unbound columns as
well. The rows of the grid will be created when you set the grid’s Data-
Source property to the source as in the straight data-binding case, because
the grid will iterate through the rows or objects in the data collection, creat-
ing a new row for each.
There are two primary ways to populate the contents of unbound col-
umns: handling the RowsAdded event or handling the CellFormatting
event. The former is a good place to set the cell’s value to make it available
for programmatic retrieval later. The latter is a good place to provide a
value that will be used for display purposes only and won’t be stored as
part of the data retained by the grid cells collection. The CellFormatting
event can also be used to transform values as they are presented in a cell to
something different than the value that is actually stored in the data that
sits behind the grid.
To demonstrate this capability, let’s look at a simple example.
At this point you have an empty Windows Forms project with a typed
data set for Customers defined.
2. Add a DataGridView and BindingSource to the form from the
Toolbox, naming them m_CustomersGrid and m_Customers-
BindingSource respectively.
3. Add the code in Listing 6.1 to the constructor for the form, following
the call to InitializeComponents.
// Subscribe events
m_CustomersGrid.CellFormatting += OnCellFormatting;
m_CustomersGrid.RowsAdded += OnRowsAdded;
// Data bind
m_CustomersGrid.DataSource = m_CustomersBindingSource;
}
This code first retrieves the Customers table data using the table
adapter’s GetData method. As discussed earlier in the book, the
table adapter was created along with the typed data set when you
added the data source to your project. It sets the returned data table
Noyes.book Page 229 Thursday, December 15, 2005 3:57 PM
member variable, and then use that index directly for comparison. I went
with the lookup approach in this sample to keep it simple and easy to
read—so you could focus on the grid details instead of a bunch of perfor-
mance-oriented code. Besides, this is a somewhat contrived example any-
way; it’s just meant to demonstrate how to create an unbound column.
If you want to set the actual stored cell values of unbound columns in
the grid, a better way to do this is to handle the RowsAdded event. As you
might guess from the name, this event is fired as rows are added to the
grid. By handling this event, you can populate the values of all the
unbound cells in a row in one shot, which is slightly more efficient than
having to handle the CellFormatting event for every cell. The Rows-
Added event can be fired for one row at a time if you are programmatically
adding rows in a loop, or it can be fired just once for a whole batch of rows
being added, such as when you data bind or use the AddCopies method of
the rows collection. The event argument to RowsAdded contains properties
for RowIndex and RowCount; these properties let you iterate through the
rows that were added to update the unbound column values in a loop. The
following method shows an alternative approach for populating the grid’s
Contact column from the previous example, using the RowsAdded method
instead of the CellFormatting event:
This code obtains the current row being set in the loop by indexing into
the Rows collection with the RowIndex property from the event argument
and a loop index offset up to the number of rows that are affected when
this event fires. It then uses the existing data in that row’s other columns to
Noyes.book Page 232 Thursday, December 15, 2005 3:57 PM
compute the contents of the current row. For a real-world application, you
could obtain or compute the value of the unbound columns in the row in
the loop instead.
object collection or data set, or you might actually make round-trips to the
server to get the data as needed. With the latter approach, you’ll need a
smart preload and caching strategy, because you could quickly bog down
the application if data queries have to run between the client and the server
while the user is scrolling. If the data being displayed is computed data,
then it really makes sense to wait to compute values until they are actually
going to be displayed.
1. Create a grid and define the columns in it that will use virtual mode.
2. Put the grid into virtual mode by setting the VirtualMode property
to true.
3. If you aren’t using data binding, add as many rows to the grid as you
want to support scrolling through. The easiest and fastest way to do
this is to create one prototype row, then use the AddCopies method
of the Rows collection to add as many copies of that prototype row
as you would like. You don’t have to worry about the cell contents
at this point, because you are going to provide those dynamically
through an event handler as the grid is rendered.
4. The final step is to wire up an event handler for the CellValue-
Needed event on the grid. This event will only be fired when the grid
is operating in virtual mode, and will be fired for each unbound col-
umn cell that is currently visible in the grid when it is first displayed
and as the user scrolls.
The code in Listing 6.2 shows a simple Windows Forms application that
demonstrates the use of virtual mode. A DataGridView object was added
to the form through the designer and named m_Grid, and a button added
to the form for checking how many rows had been visited when scrolling
named m_GetVisitedCountButton.
Noyes.book Page 235 Thursday, December 15, 2005 3:57 PM
e.Value = m_Data[e.RowIndex].Val;
}
else if (e.ColumnIndex == 2)
{
Random rand = new Random();
e.Value = rand.Next();
}
}
public int Id
{
get { return m_Id; }
set { m_Id = value; }
}
}
for each cell that is revealed through the scrolling operation. The constructor
also subscribes a handler for the button click, which lets you see how many
rows the CellValueNeeded event handler was actually called for.
After that, the constructor calls the InitData helper method, which
creates a collection of data using a List<T> generic collection that contains
instances of a simple DataObject class, defined at the end of Listing 6.2.
The DataObject class has two integer values, Id and Val, which are pre-
sented in the grid. The collection is populated by the InitData helper
method with one million rows. The Id property of each object is set to its
index in the collection, and the Val property is set to two times that index.
continues
Noyes.book Page 238 Thursday, December 15, 2005 3:57 PM
After that, Windows Forms event handling takes over for the rest of the
application lifecycle. Because the grid was set to virtual mode, the next
thing that happens is the OnCellValueNeeded handler will start to get
called for each cell that is currently displayed in the grid. This method is
coded to extract the appropriate value from the data collection based on
the row index and column index of the cell that is being rendered for the
first two columns. For the third column, it actually computes the value of
the cell on the fly, using the Random class to generate random numbers. It
also sets a flag in the m_Visited collection—you can use this to see how
many rows are actually being rendered when users scroll around with the
application running.
to supply a tiny percentage of the actual cell values unless the user does an
extensive amount of scrolling in the grid. This is why virtual mode is par-
ticularly nice for computed values: you can avoid computing cell values
that won’t be displayed.
If you run this example and scroll around for a bit, then click the Get
Visited Count button, you will see how many rows were actually loaded.
For example, I ran this application and scrolled through the data from top
to bottom fairly slowly several times. While doing so, I saw smooth scroll-
ing performance that looked like I was actually scrolling through the
millions of rows represented by the grid. However, in reality, only about
1,000 rows were actually rendered while I was scrolling.
What if you want to support editing of the values directly in the grid?
Maybe you are just using virtual mode to present a computed column with
a relatively small set of data, and you want to use that column’s edited
value to perform some other computation or store the edited value.
Another event, CellValuePushed, is fired after an edit is complete on a
cell in a virtual mode grid. If the grid doesn’t have ReadOnly set to true,
and the cells are of a type that supports editing (like a text box column),
then the user can click in a cell, hesitate, then click again to put the cell into
editing mode. After the user has changed the value and the focus changes
to another cell or control through mouse or keyboard action, the Cell-
ValuePushed event will be fired for that cell. In an event handler for that
event, you can collect the new value from the cell and do whatever is
appropriate with it, such as write it back into your data cache or data store.
when virtual mode will be needed. If you are having scrolling performance
problems in your application, or you want to avoid the memory impact of
holding computed values for large numbers of rows in memory, you can
see if virtual mode solves your problems. You will still need to think about
your data retrieval and caching strategy, though, to avoid seriously ham-
pering the performance of your application on the client machine.
Because each built-in column type is different in subtle ways, it’s best to
cover them one at a time. However, since all of the cell types contained by
the column types derive from the same base class, there are a number of
properties from the base class that you’ll use for controlling and accessing
cell content. The properties of the DataGridViewCell base class are
described in Table 6.1.
continues
Noyes.book Page 244 Thursday, December 15, 2005 3:57 PM
continues
Noyes.book Page 248 Thursday, December 15, 2005 3:57 PM
There are a number of built-in column types that are available for using
with the DataGridView control corresponding to the most common con-
trol types that developers want to include in a grid. The following subsec-
tions describe each of the built-in column types and what is involved in
using them.
DataGridViewTextBoxColumn
This is the default type of column (as described earlier in this chapter), and
it displays text within the contained cells, which are of type DataGrid-
ViewTextBoxCell. Data that is bound to this column type and values set
on the cell have to be of a type that can be converted to a string.
This column type supports editing if the ReadOnly property is true
(the default) and the focus in on the cell. To enter editing mode, press F2,
type in characters, or click in the cell. This embeds a separate editing con-
trol of type DataGridViewTextBoxEditingControl, which derives from
Noyes.book Page 250 Thursday, December 15, 2005 3:57 PM
TextBox. This type enables in-place editing for the grid value, like you are
used to for text box controls. The value in the text box is treated as a tran-
sient value until the focus leaves the cell; then the CellParsing event
fires, and the value is pushed into the underlying data store if data bound
or the CellValuePushed event fires if in virtual mode.
DataGridViewButtonColumn
This column type displays cells of type DataGridViewButtonCell, which
is sort of a fancy form of read-only text cell. A button cell lets you have a
button-push experience embedded in the grid, which you can use to trig-
ger whatever action makes sense for your application. The button cell ren-
ders itself with a border that looks like any other button control, and when
the user clicks on it, the cell renders again with a depressed offset so that
you get an action like a button. To handle the “button click,” you need to
handle the CellClick event on the grid, determine if it was a button cell
that was clicked, and then take the appropriate action for your application.
This involves taking the event argument from the CellClick event, check-
ing its ColumnIndex property against the column index of button columns
in your grid, and then calling the button click handling code from there
based on the row index, or the contents of that cell or others in that row.
DataGridViewLinkColumn
Like the button column, this is another form of rendering a text cell that
gives the user a visual cue that clicking on it will invoke some action. This
column type contains cells of type DataGridViewLinkCell and renders
the text in the cell to look like a hyperlink. Typically, clicking on a link
“navigates” the user somewhere else, so you might use this kind of column
if you are going to pop up another window or modify the contents of
another control based on the user clicking on the link. To do so, you handle
the CellClick event as described previously for the button, determine if
you are in a cell containing a link, and take the appropriate action based on
that link. You will have to derive the context of what action you should
take either from the cell’s contents or other cells in that row or column.
Noyes.book Page 251 Thursday, December 15, 2005 3:57 PM
DataGridViewCheckBoxColumn
By now you are probably picking up the pattern, and as you would guess,
this column type contains cells of type DataGridViewCheckBoxCell. This
cell type renders a CheckBox-like control that supports tri-state rendering
like a CheckBox control.
The values that this cell type supports depend on whether you set the
cell or column type into ThreeState mode or not. If the ThreeState prop-
erty is set to false (the default), then a value of null or false will leave
the check box unchecked; a value of true will check the box. If ThreeState
is set to true, then the Value property of the cell can be null or one of the
CheckState enumeration values. If null and ThreeState is true, then
the check box will be rendered in the indeterminate state (a square filling
it). The CheckState enumeration values are Unchecked, Checked, and
Indeterminate, which are self-explanatory. The cell’s Value property can
be set explicitly through programmatic code that accesses the cell in the
Cells collection of the row, or it can be set through data binding.
DataGridViewImageColumn
This column, not surprisingly, contains cells of type DataGridView-
ImageCell, which support the rendering of images directly within the
grid’s cells. This cell type provides a very handy and easy-to-use capability
in the DataGridView control that used to be fairly painful to achieve with
the DataGrid control. This column type exposes Image and ImageLayout
properties in addition to the usual base class properties. Setting the col-
umn’s Image property results in that image being displayed by default for
all the cells in that column. The ImageLayout property takes a DataGrid-
ViewImageCellLayout enumeration value. The values of this enumera-
tion and their effect on the rendering of an image in the grid are described
in Table 6.3.
In addition to setting a default image at the column level, you can set
the Value property at the cell level, either explicitly through code or
implicitly through data binding. The value can be set to any type that can
be converted to an Image object for display in the cell. Natively in .NET,
this means that the value can either be an Image or a byte array that con-
tains a serialized Image.
Noyes.book Page 252 Thursday, December 15, 2005 3:57 PM
Value Effect
NotSet This is the default and indicates that the layout behavior has not been
explicitly specified. The resulting behavior of the cell is the same as if
Normal had been explicitly set.
Normal The image is rendered at its native size and centered in the cell.
Depending on the size of the cell, any portions of the image that are
outside the bounds of the cell will be clipped.
Stretch The image is stretched or shrunk in both width and height so that it
fills the cell and no clipping occurs. No attempt is made to maintain
the aspect ratio (width/height) of the image.
Zoom The image is resized so that it fits within the cell without clipping, and
the aspect ratio (width/height) is maintained so that no distortion of
the image occurs.
DataGridViewComboBoxColumn
This column type contains cells of type DataGridViewComboBoxCell,
which renders itself like a standard ComboBox control within the cell. This
column type is definitely the most complex built-in column type for the
DataGridView, and it exposes a number of properties that drive its behav-
ior, as described in Table 6.4.
continues
Noyes.book Page 254 Thursday, December 15, 2005 3:57 PM
The combo box cells support edit mode, and users can type in a value
for autocompletion purposes or select values from a drop-down list. When
in edit mode, this cell type hosts a control that derives from the ComboBox
control, so all of its functionality is exposed when the cell is switched into
edit mode.
The Value property represents the currently selected value in the
combo box. It may contain the displayed text value in the combo box, or it
may contain the underlying ValueMember value for the selected item,
depending on what you set for the DataSource, DisplayMember, and
ValueMember properties. The FormattedValue property, inherited from
the base class, always contains the formatted text for the selected item that
is being displayed in the combo box.
Data binding this column type or the cells in it works just like data bind-
ing a standalone ComboBox control. You set the DataSource, Display-
Member, and ValueMember properties, and the items in the data source
collection are rendered in the drop-down list using the value of the data
member that is identified as the display member:
Noyes.book Page 255 Thursday, December 15, 2005 3:57 PM
toCountryColumn.DataSource = m_CountriesBindingSource;
toCountryColumn.DisplayMember = "CountryName";
toCountryColumn.ValueMember = "CountryID";
The sample code that accompanies this chapter contains a simple appli-
cation called ColumnTypes that demonstrates how the code interacts with
each of the built-in column types described in this chapter.
This code checks to see if the column being painted has an index less
than zero, which indicates that the row header is being painted. The col-
umn index of the row headers is –1, and the row index of the column head-
ers is also –1. You cannot index into the Cells collection on the row with
these values, but you can use them as a flag in the CellPainting event to
know when it is a header that is being painted.
Additionally, you can set the CellHeader property to an instance of a
class that derives from DataGridViewCell, and then that cell type will be
used when the header cells are rendered. You can derive your own class
from the cell base class and do whatever kind of custom painting, format-
ting, or setting of styles there that makes sense.
As mentioned earlier, when working with a text box column, users can
start editing a cell by putting the focus into the cell with the mouse, arrow
keys, or by pressing the F2 key when the mouse pointer is in the cell. If
users then start typing characters, the current contents of the cell will be
overwritten. When they change the focus to another cell, this completes the
editing process.
The first thing that happens that you might want to handle is that the
CellParsing event fires. Like its CellFormatting counterpart, this event
gives you an opportunity to intercept the value that was entered into the
cell while in edit mode, either to handle storing that value somewhere
yourself or to transform it into some other value before it is stored.
If the cell is data bound, and if the data source supports editing the
data objects in the collection, the data will automatically be pushed back
Noyes.book Page 257 Thursday, December 15, 2005 3:57 PM
into the underlying data source. If the cell is a button or link cell, however,
you won’t be able to edit the contents in the first place because they don’t
support editing. If the cell is a combo box cell, editing is done by selecting
a value in the drop-down list or overtyping the current selection if the cell
has its DisplayStyle property set to ComboBox. This changes the cell’s
value when editing is complete (when the focus moves off the cell) and
results in the same action as if that value had been typed into a text box
cell. If the grid is in virtual mode, you will need to handle the CellValue-
Pushed event to grab the value that was entered and do what you need to
with it.
When a cell switches into edit mode, an event named Editing-
ControlShowing fires. This event passes an event argument that lets you
get a reference to the editing control itself. The built-in cell types that sup-
port editing (text box, combo box, and check box cell types) create an
instance of an editing control that derives from their normal Windows
Forms counterparts (TextBox, ComboBox, and CheckBox, respectively)
and display that control as a child control inside a panel inside the cell. If
you create a custom cell type that supports editing, then you will want to
follow a similar approach. Through the EditingControlShowing event,
you can get a reference to the editing control in use and can tap into its
event model to respond to edits in realtime. For example, if you want to
dynamically react to selections in a combo box column while the control is
still in edit mode and the selected value hasn’t been pushed yet into the
cell’s underlying value (meaning the CellParsing event hasn’t yet fired),
you could use the EditingControlShowing event to hook things up:
public Form1()
{
InitializeComponent();
m_Grid.EditingControlShowing += OnEditControlShowing();
}
Keep in mind that the Flag column in the Countries table is actually a
byte array containing the bits of the saved image file. The automatic for-
matting of the image column kicks in here to present the image in the
same way that was discussed for a PictureBox control in Chapter 4. The
ColumnTypes sample in the download code demonstrates this technique.
continues
Noyes.book Page 260 Thursday, December 15, 2005 3:57 PM
The Fill mode is very powerful for automatically maximizing the use
of the grid real estate, but it can be a little complicated to understand. Basi-
cally, if you set the mode for all columns to Fill, each of the columns will
have their width set equally, and the columns will fill the grid boundary
Noyes.book Page 261 Thursday, December 15, 2005 3:57 PM
public Form1()
{
InitializeComponent();
m_CustomersGrid.Columns["CustomerID"].Width = 75;
m_CustomersGrid.Columns["CompanyName"].AutoSizeMode =
DataGridViewAutoSizeColumnMode.Fill;
m_CustomersGrid.Columns["CompanyName"].FillWeight = 10;
m_CustomersGrid.Columns["ContactName"].AutoSizeMode =
DataGridViewAutoSizeColumnMode.Fill;
m_CustomersGrid.Columns["ContactName"].FillWeight = 20;
}
the divider between cells of that column or row and the adjacent one (to
the right or below).
The following code shows a simple example of freezing both a column
and a row and setting the divider width:
m_ProductsGrid.Columns["ProductName"].Frozen = true;
m_ProductsGrid.Columns["ProductName"].DividerWidth = 3;
m_ProductsGrid.Rows[1].Frozen = true;
m_ProductsGrid.Rows[1].DividerHeight = 3;
column, and will expose additional properties based on the column type
and cell type. If you define custom column types and include them in your
project, they will show up as options for new columns or for configuring
columns through this dialog.
The Add Column dialog (see Figure 6.5) lets you add a new data-bound
or unbound column to the grid. If you are adding a data-bound column,
you can select from the columns available in the currently selected data
source. You will first have to set the data source to an appropriate collec-
tion of data either through the Smart Tag or through the DataSource
property in the Properties window. If you are adding an unbound column,
Noyes.book Page 265 Thursday, December 15, 2005 3:57 PM
then you just specify the name of the column, the type of the column, and
the header text. When you click the Add button, the column is added to the
grid, and the dialog remains open so you can quickly define multiple new
columns.
Configuring the columns through these dialogs writes all the code for
you that has been covered earlier in this chapter for defining columns and
controlling their runtime behavior.
The Enable Adding check box on the DataGridView Smart Tag sets the
AllowUserToAddRows property to true if checked, which displays a new
empty row at the bottom of the grid. This lets users add a new row to the
data collection by typing new values into the cells. The ability to support
this depends on whether the grid is data bound, and, if so, whether the
underlying object collection supports adding new items to the collection
(see the discussion in Chapter 7). Likewise, the Enable Editing check box sets
the ReadOnly property, which affects whether users can edit the contents of
the grid in place, and Enable Deleting sets the AllowUserToDeleteRows
property. The Enable Column Reordering check box sets the AllowUser-
ToOrderColumns property, whose behavior is described in the next section.
The Dock in parent container link is only available if you first drag and
drop a grid control onto a form. It does exactly what it says—it simply sets
the Dock property to Fill.
Noyes.book Page 266 Thursday, December 15, 2005 3:57 PM
In addition to the common properties and behaviors that you can config-
ure through the Smart Tag, there are a bunch of other properties and events
that you can configure at design time through the Properties window. Set-
ting any of these properties generates appropriate code in the designer-
generated partial class for the form in the InitializeComponent method.
Most notably, you can configure any of the data-binding properties through
the Properties window. You’ll probably want to set styles using the Proper-
ties window, because you can preview the results of those settings in the
designer to make sure you are getting what you expect. Styles are discussed
in more detail at the end of this chapter.
Column Reordering
Column reordering is a slick built-in behavior of the grid that lets users
change the display order of columns in the grid at runtime. Because differ-
ent users of an application often pay more attention to some columns in a
grid than others, users commonly request to set the order of the columns
displayed in the grid themselves. While you could support this functional-
ity by programmatically removing columns from the grid and then insert-
ing them back in the new position, that requires a fair amount of tedious
code to have to write for a common use case. So the Windows Client team
was nice enough to build functionality for this right into the grid control.
The way this works is that if the AllowUserToOrderColumns property
is set to true and the user clicks and drags on a column header, the grid
lets them drag and drop the column to the position where they would like
it to display. The columns to the right of the drop position will move one
position to the right, and the columns surrounding the original location of
the dragged column will move to be adjacent after the column has been
moved. Figure 6.6 shows this in action. In this case, the QuantityPerUnit
column was clicked on and is being dragged to the left. A gray box is
drawn the size of the column’s header cell you are dragging. When you
move the cursor to one side of another column, the border between that
column and the adjacent one darkens, indicating where the column you
are dragging will be placed if you release the mouse button.
Noyes.book Page 267 Thursday, December 15, 2005 3:57 PM
continues
Noyes.book Page 268 Thursday, December 15, 2005 3:57 PM
This code isn’t specific to the data source in any way. The key facets
here are that the code in the form Load event handler sets the Allow-
UserToOrderColumns property to true, allowing the dynamic changing
of DisplayIndex for columns through drag-and-drop operations. I then
added a CacheDisplayOrder helper method that is called by the
Form.Closing event handler, and a SetDisplayOrder helper method
that is called when the form loads.
CacheDisplayOrder first collects all the display index values for each
of the grid’s columns and puts them into an integer array. It then creates an
isolated storage file stream and writes the array to that stream using the
XmlSerializer class. The SetDisplayOrder method does the reverse: it
first checks to see if the file exists, and if so, reads the array back in and uses
it to set the DisplayIndex on each column in the grid.
I also want the cell value to be able to handle integers as long as they are
within the corresponding numeric values of the enumeration, so that I can
support the common situation where enumerated types are stored in a
database as their corresponding integer values. The code in Listing 6.4
shows the StatusCell class implementation.
case StatusImage.Yellow:
resource = "CustomColumnAndCell.Yellow.bmp";
break;
case StatusImage.Red:
resource = "CustomColumnAndCell.Red.bmp";
break;
default:
break;
}
Assembly loadedAssembly = Assembly.GetExecutingAssembly();
Stream stream =
loadedAssembly.GetManifestResourceStream(resource);
Image img = Image.FromStream(stream);
cellStyle.Alignment =
DataGridViewContentAlignment.TopCenter;
return img;
}
}
}
resource name corresponding to the image for that status value. You embed
bitmap resources in the assembly by adding them to the Visual Studio
project and setting the Build Action property on the file to Embedded
Resource. The code then uses the GetManifestResourceStream method
on the Assembly class to extract the bitmap resource out of the assembly,
sets the alignment on the cellStyle argument passed into the method,
and then returns the constructed image as the object from the method. The
object that you return from this method will be the one that is passed down-
stream to the Paint method as the formatted value to be rendered. Because
this doesn’t override the Paint method, the implementation of my Data-
GridViewImageCell base class will be called, and it expects an Image
value to render.
continues
Noyes.book Page 274 Thursday, December 15, 2005 3:57 PM
You can see from the implementation of StatusColumn that you first
need to derive from the DataGridViewColumn class. You implement a
default constructor that passes an instance of your custom cell class to the
base class constructor. This sets the CellTemplate property on the base
class to that cell type, making it the cell type for any rows that are added to
a grid containing your column type.
The next thing the class does is define a public property named
DefaultStatus. This lets anyone using this column type to set which of
the three StatusImage values should be displayed by default if no value is
explicitly set on the grid through data binding or programmatic value set-
ting on a cell. The setter for this property changes the member variable that
keeps track of the current default. The DefaultStatus property on the
Noyes.book Page 275 Thursday, December 15, 2005 3:57 PM
and a StatusColumn. Note that you can set the values with either the enu-
merated value or with an appropriate integer value.
Value Description
CellSelect This mode lets you select one or many cells in the grid
using the mouse or keyboard. If you click in any cell,
just that cell will be selected. You can click and drag,
and contiguous cells that you drag over will also be
selected. If you click in one cell, then Shift-click in
another, you will select the entire contiguous set of cells
from the first click to the second. You can even select
noncontiguous cells by holding down the Ctrl key while
you click cells. This is the default selection mode.
FullRowSelect Clicking in any cell in the grid will select all of the cells
in the entire row that contains the cell and will deselect
any cells outside the row.
FullColumnSelect Clicking in any cell in the grid will select all of the cells
in the entire column that contains the cell and will dese-
lect any cells outside the column.
RowHeaderSelect Clicking on the row header cell will select the entire
row, but otherwise this selection mode behaves like
CellSelect. This is the mode set by the designer for
a grid when you add it to a form.
ColumnHeaderSelect Clicking on the column header cell will select the entire
column, but otherwise this selection mode behaves like
CellSelect.
cells as you need them (which you could do with virtual mode) to avoid
the overhead of maintaining a large number of cells, their contents, and
their selections if the grid will be sparsely populated.
To get the row numbers to display in the row headers, I handled the
RowAdded event and set the header cell value in that handler:
Another selection mode you might want to support is to have hot cells,
meaning that the selection of cells changes as you move the mouse around
the grid without having to click. To do this, you could just handle the
CellMouseEnter and CellMouseLeave events, selecting and deselecting
the cell under the mouse in those handlers, respectively.
set the border colors on the cells that have been colored in to show a black
border. However, there is really no way to accomplish this just through cell
styles, since the border styles available are only 3D effects and are applied
at the grid level for entire cells, not for individual sides of a cell. But, as
always, you can almost always accomplish what you need through custom
painting or custom cell type definition.
Noyes.book Page 284 Thursday, December 15, 2005 3:57 PM