Introduction

Visual Studio .NET comes with an out-of-the-box grid control called DataGrid. Complete with data-binding and many nifty features, it seems like a handy control. However, once you start using it, you may find that its usage is somewhat cumbersome and - in many real-life situations - downright puzzling. This article attempts to lead the beginner through the ropes of using DataGrid, including:

Data binding complex DataSets and OO class hierarchies

Adjusting columns and various "style" issues

Building usable context menus which behave according to position

Refreshing data updated outside of the grid code

Test Case

DataGrid is extremely easy to use when addressing a single table. Its navigation abilities give a very sexy (although I tend to be a skeptic regarding usefulness) view of table relations. In order to avoid the obvious, I designed a simple yet realistic domain with structure and requirements that do not fit the simple binding abilities of DataGrid. I call this domain Cars and I will use it for examples throughout this article:

A Car has 3 attributes: licensePlate, carType and price

A CarType has 2 attributes: name and manufacturer

A Manufacturer has a name

This domain can be found in the attached ZIP in 3 forms:

A Jet Database (Cars.mdb)

An XML schema (ObjectCars\CarDataSet.xsd)

A set of class files (ObjectCars\Car.vb,CarType.vb, CarManufacturer.vb)

The requirement is simple: write a grid-like screen to manage car prices. A snapshot was given above.

Data Binding

Why does DataGrid binding fall short? DataGrid defines its DataSource as a single IList, be it DataTable or object Array (multiple tables will create a "navigate" interface). While it is easy to infer all data relevant for a car by walking through relations/references, DataGrid column binding does not support a "dot" notation. In other words, when binding to a list of Cars, I can display Car.licensePlate, but not Car.carType.name. Let us walk through 3 possible solutions, each with its pros and cons.

Join Based DataBinding

When it comes to data querying, there is nothing easier or more maintainable than using SQL. A simple SQL join statement can feed the relevant data into a DataTable fashioned for our use. Pros: fast, easy, simple and maintainable. Don't stop reading here, because there are several flaws…

While you easily got the data to display, updating is now a major hassle: you need to "break" back into the original table structure before you can update your data base

In the 3-tier scenario there is something inherently wrong with requesting the server for data structured specifically for your screen. In cases where you don't own the server it is not only wrong, it is impossible.

The inherent data redundancy typical to a join (the same data exists in many rows) is error-prone when it comes to row creation.

An example of this approach can be found in the attached ZIP: SingleQueryCars\SQLJoinBasedForm.vb.

Multiple Table Based DataBinding

Microsoft tutorials emphasize the DataSet ability to store complex table structures allowing higher client independence and fewer round trips. Once we embrace this philosophy, we are required to join the tables on the client side: no SQL. Unfortunately, .NET does not include a Joiner utility class, so we need to join data in our code. The procedure is quite simple:

Essentially this is the same as what we did in an SQL join. The main benefit is client/server de-coupling, i.e. no server coding for grid purposes. The biggest drawbacks are client-side performance and additional procedural code. An example of this approach can be found in the attached ZIP: JoinBasedCars\LoopBasedJoinForm.vb.

Object List Data Binding

In many cases, it is best to leave DataSets as a link to the underlying DB and perform data manipulation using a class hierarchy (a.k.a. an "Object Domain"). The more logic in your application, the better this approach will serve you. Furthermore, in some cases Object Models are all we have. An example is when our server insists on handing data in Object form. Since "dot notation" is not supported, how can we display properties of anything but our "root objects" (i.e. cars)?

The solution is simple, although it may seem cumbersome at first: we create a new class (usually referred to as the "viewer" class), which wraps the root object and exports all needed data as properties. For instance, in our example we would code a CarViewer class wrapping a Car object and exporting the properties licensePlate, typeName and manufacturerName. We would then bind an ArrayList of these to the DataGrid.

It turns out that this solution is extremely powerful, since it gives us a natural place for a user-interface related code that is non-trivial: things like calculated attributes, complex use cases (how about "switch plates with another car"), etc. In fact, it usually makes sense to use viewer-based grids instead of performing "procedural joins," even if you have the data in a DataSet and not an Object Domain.

Not surprisingly, the author did not invent this concept. It is an adaptation of a well-known paradigm called MVC (Model-View-Controller) and you are welcome to read any of the multitudes of excellent articles available on the Internet. An example of this approach can be found in the attached ZIP: ObjectCars\ViewerBasedForm.vb.

Refreshing the Grid

No matter how you perform data binding, if your application views dynamic data you are bound to refresh the grid at some time. It turns out that this is another trivial task that was made "un-obvious." Here's how:

Get the DataGridCurrencyManager

Call its refresh method

Note the parameter to BindingContext. This is where most people fail. It should be a reference to the exact object you bound.

Formatting the Grid

Now that we have all relevant data bound, it's a good idea to make it human-readable. Grid formatting is relatively easy, but from the amount of discussion devoted to it in news groups, one can infer that Microsoft did not expose it very neatly. I will try to sort the basics and give some pointers to more advanced stuff.

Basic Column Formatting

All grid formatting is centered about the TableStyles collection accessible via the grid property window. Here is how it works:

A style defines most of the grid formatting properties, including column format (via a collection called GridColumnStyles)

At any given moment, the grid adheres to one style selected according to the style MappingName property

Once you understand this, many of the basic tasks are, well, basic. Here are some examples:

Column caption, width, Read/Write, control type (text box / check box) and more are defined in the column style

Column order is determined by GridColumnStyle order

If we want to "hide" a column, we don't map it to any column style

The only trick left is to determine the right mapping name:

When grid shows a DataSetDataTable, use the table name as defined in the schema

Examples for this can be found in all 3 forms supplied in the attached ZIP. Of course, all these properties are accessible at runtime, allowing easy implementation of features such as "re-arrange columns," "hide column," etc.

Advanced Formatting

Unfortunately, some extremely useful features we expect of a DataGrid are not easily implemented and require more advanced programming. Chief among these are:

The ability to use controls other than text box and check box

The ability to dynamically control colors and fonts at cell level

Once we understand that the heart of formatting is the DataGridColumn class, then it is evident that to achieve advanced formatting we need to extend it in a manner that fits us. Excellent pointers for such work can be found here (combo in grid).

Context Menus

In real life applications, grids will usually have more than one context menu: column header menu will differ from cell menu; row header menu might differ from both and sometimes the menu may be affected by selection areas. Although DataGrid has only one ContextMenu, managing this kind of behavior is simple enough:

Create all needed menus, either on the designer or dynamically

Write a handler for the DataGrid.MouseDown event:

Check for right-click

Compute the row/column clicked

Set the DataGrid.ContextMenu according to context

Note that this works since your handler is called before the context menu is displayed. Here is an example that shows a context menu only when clicked over a cell. It stores cell coordinates for later use by the context menu handlers:

The Attached Source Code

SingleQueryCars is a read-only grid based on an SQL join; it mainly shows column customization.

JoinBasedCars has similar functionality, but it also exemplifies client side join of several DataSet tables

ObjectCars is a bit more interesting (as I believe this is the best way most of the time); it demonstrates all discussed and a bit more, including:

Simple mapping a data set to an object domain

Viewer management

Column formatting

Context menu usage

Adding lines, deleting lines, hiding lines

Summary

There are already several .NET grids on the market which look much better than DataGrid and more will come. However, when used correctly, DataGrid can still take you a long way to a usable user interface and it is certainly worth your while experimenting with it further.

Share

About the Author

Omri started coding way back in the good ol' Apple ][ days. His first commercial (acutally governmental) software package was released 1986.
In the 90's Omri programmed C and C++, and managed larger development teams using MFC/COM.
In the last 3 years he is into Java server programming, and while he loves the language and community, he has many reservations. Maybe this is why .Net feels like comming back home...

Comments and Discussions

First things first, how are you populating the DataGrid now? By creating a DataTable or using Class ojects?

If you are using DataTable, you can comfortably store that data to database, whenever you wish. If you are binding Class objects, you may need to convert objects to DataRows and then can save to Database.

In the section 'Advanced Formatting' you have an intended link for 'dynamic control of background color' but the link is not set up. Can you tell me the link for that article? Thanks, I'm trying to control the background color for individual cell.

Within a Windows Form I have a datagrid with a dataset as its datasource. I also have several textboxes on the form that are databound to the fields of the dataset. The form has a button that causes the current record to be deleted from the datagrid and thereby the dataset as well. As long as it is the last record in the datagrid that is being deleted, everything works just fine. However, if a record in the middle of the datagrid is deleted, the datagrid disappears and a large box with an "X" inside it appears in its place. When I start the application again, all is as it should be: the datagrid properly displays the contents of the dataset and the record that was deleted has been removed from the dataset and thereby the database. Any ideas about this would be greatly appreciated. The code that does the deletion is shown below:

I have two questions.
First: How do I hide a column in the example? I have set up a new context menu with an item “Hide Column”. This menu will appear only if the mouse right clicks on some column header. But how to hide the column?
Second: I want to put a new button in the example with the possibility to save the only VISIBLY data in the grid to a new dataset. When the user has hidden all the data from that is of no interest, how do I get all visible data from the grid?

I am having trouble with being able to bind Boolean values from an xml file that can have an ever increasing number of elements of boolean value.
In other words I am trying to make a datagrid check box register, but I need some advice on how this is done Programatically to as everytime the gird is populated it reads it as null even though I can set the details in code not to accept null and amend the null value to false?

I have a form's datagrid from System.Windows.Form.DataGrid. I am making datagrid's caption and column header visible. Now my question is that what I need to do so that when cursor moves on Caption OR Column header it shows clickable cursor (like a hand).

I have problems to bind a collection of business objects
that contains a nested object within to a datagrid, for
example

public class User

Attributes
.
.
.
Other Propertys
.
.
.
Nested object property that exposes in a datagrid column
public property Employee as Employee
get
return m_Employee
end get
end property

public class Employee

public property Name as String
public property SocialSecurityNumber as Integer

when I try to bind a strongly typed collection of object
Users to a datagrid, the property that exposes the nested
object dont show the values. The collection implements
the following interfaces ITypedList, IListSource,
IBindingList. I would like to know if exist a solution
for this problems that dont uses a middle value object to
exposes the nested object, thanks.

The grid is correctly sorted, but moving changing the current row on the grid does not change the unitsCurrencyManager1.Position value and the textBoxNotes1 ends up pointing at same record it was pointing to before.

Any ideas, I'm kinda stuck...

Note: dsUnits is a dataset, generated using the "Generate Dataset" from the "Data" menu when in design mode. I'm using the .net framework 1.1

I personaly believe in implementing a "viewer" class (I usualy use the highly inspired name "GridLine") and holding these in the ArrayList.
Then sort is implemented by sorting the ArrayList. This also allows grouping to be made(by subclassing GridLine and hiding group members), and other such fun stuff.

Ok,
but how sort in datagrid on click column header?
This sort is not supported if datasource is arraylist.
Like in your example #3 datagrid column sort is not available, regardless
'AllowSorting' property is set to true.

If you left click a cell in your project followed by a right click, you get the default contextMenu (undo/cut/copy/...) Do you by chance know how to merge your contextMenu onto the default? I know that there is a MergeMenu() method. I just don't know how/where to do it.

1. There is 4th way of showing vaslues from other data tables in grid. This is the way similar to second you've described, but one of cons is smaller (requirement for own code): in place of 'For each' construct where values from parentTable are copied to mainTable showed in grid You can use .Expression property of DataColumn of DataSet. You can set such expression in design-time or in run-time (one drawback is moment of time when expressions are calculated - in AcceptChanges method, sometimes it is to late, for instance when You editing row and expression is based on other columns you've just edit)

2. You've not explicitly written then 3rd way (MVC like) has the same drawback as 2nd (even multiplied): big piece of own code to write. Who has a time (like said one guy in Matric Reactivation )?

Thanks for the code sample. It helped me past a piece of magic in binding to an IList implementation that you did not mention, and does not seem to be mentioned in the documentation.

It seems the MappingName attribute of the DataGridTableStyle object must be set to the class name of the collection (eg. "ArrayList"). If it is not set or set to something else, the DataGrid ignores the DataGridTableStyle and creates one containing all the properties of the class in the collection.

Thanks for the feedback. Actually I do mention this in the article (Quote:"...When grid shows an object data structure use the structure data type (i.e. ArrayList)..."), but it is certainly one bit of imformation that is worth emphasizing.