The DataGridView Control


The DataGrid control that has been available from the initial release of .NET was functional, but had many areas that made it unsuitable for use in a commercial application - such as an inability to display images, dropdown controls, or lock columns, to name but a few. The control always felt half-completed, so many control vendors provided custom grid controls that overcame these deficiencies and also provided much more functionality.

With .NET 2.0, you now have an additional Grid control - the DataGridView. This addresses many of the deficiencies of the original control, and adds significant functionality that has to this point only been available with add-on products.

The new control has binding capabilities similar to the old DataGrid, so it can bind to an Array, DataTable, DataView, or DataSet class, or a component that implements either the IListSource or IList interface. The DataGridView control gives you a variety of views of the same data. In its simplest guise, data can be displayed (as in a DataSet class) by setting the DataSource and DataMember properties - note that this new control is not a plugin replacement for the DataGrid, so the programmatic interface to it is entirely different from that of the DataGrid. This control also provides more complex capabilities, which are discussed in the course of this chapter.

Displaying Tabular Data

Chapter 19 introduced numerous ways of selecting data and reading it into a data table, although the data was displayed in a very basic fashion using Console.WriteLine().

The following example demonstrates how to retrieve some data and display it in a DataGridView control. For this purpose, you’ll build a new application, DisplayTabularData, shown in Figure 29-1.

image from book
Figure 29-1

This simple application selects every record from the Customer table in the Northwind database, and displays these records to the user in the DataGridView control. The following snippet shows the code for this example (excluding the form and control definition code):

  #region Using directives using System; using System.Configuration; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Windows.Forms; #endregion namespace DisplayTabularData {   partial class Form1: Form   {     public Form1()     {       InitializeComponent();     }     private void getData_Click(object sender, EventArgs e)     {       string customers = "SELECT * FROM Customers";       using (SqlConnection con =                new SqlConnection (ConfigurationSettings.                  ConnectionStrings["northwind"].ConnectionString))       {         DataSet ds = new DataSet();         SqlDataAdapter da = new SqlDataAdapter(customers, con);         da.Fill(ds, "Customers");         dataGridView.AutoGenerateColumns = true;         dataGridView.DataSource = ds;         dataGridView.DataMember = "Customers";       }     }   } } 

The form consists of the getData button, which when clicked calls the getData_Click method shown in the example code.

This constructs a SqlConnection object, using the new ConnectionStrings property of the ConfigurationSettings class. Subsequently a data set is constructed and filled from the database table, using a DataAdapter object. The data is then displayed by the DataGridView control by setting the DataSource and DataMember properties. Note that the AutoGenerateColumns property is also set to true, because this ensures that something is displayed to the user. If this flag is not specified, you need to create all columns yourself.

Data Sources

The DataGridView control provides a flexible way to display data; in addition to setting the DataSource to a DataSet and the DataMember to the name of the table to display, the DataSource property can be set to any of the following sources:

  • An array (the grid can bind to any one-dimensional array)

  • DataTable

  • DataView

  • DataSet or DataViewManager

  • Components that implement the IListSource interface

  • Components that implement the IList interface

  • Any generic collection class or object derived from a generic collection class

The following sections give an example of each of these data sources.

Displaying Data from an Array

At first glance this seems to be easy. Create an array, fill it with some data, and set the DataSource property on the DataGridView control. Here’s some example code:

  string[] stuff = new string[] {"One", "Two", "Three"}; dataGridView.DataSource = stuff; 

If the data source contains multiple possible candidate tables (such as when using a DataSet or DataViewManager), you need also to set the DataMember property.

You could replace the code in the previous examples getData_Click event handler with the preceding array code. The problem with this code is the resulting display (see Figure 29-2).

image from book
Figure 29-2

Instead of displaying the strings defined within the array, the grid displays the length of those strings. The reason for this is that when using an array as the source of data for a DataGridView control, the grid looks for the first public property of the object within the array and displays this value rather than the string value. The first (and only) public property of a string is its length, so that is what is displayed. The list of properties for any class can be obtained by using the GetProperties method of the TypeDescriptor class. This returns a collection of PropertyDescriptor objects, which can then be used when displaying data. The .NET PropertyGrid control uses this method when displaying arbitrary objects.

One way to rectify the problem with displaying strings in the DataGridView is to create a wrapper class:

  protected class Item {    public Item(string text)    {       _text = text;    }    public string Text    {       get{return _text;}    }    private string _text; } 

Figure 29-3 shows the output when an array of this Item class (which could just as well be a struct for all the processing that it does) is added to your data source array code.

image from book
Figure 29-3

DataTable

You can display a DataTable within a DataGridView control in two ways:

  • If you have a standalone DataTable, simply set the DataSource property of the control to the table.

  • If your DataTable is contained within a DataSet, you need to set the DataSource to the data set and the DataMember property should be set to the name of the DataTable within the data set.

Figure 29-4 shows the result of running the DataSourceDataTable sample code.

image from book
Figure 29-4

Note the display of the last column; it shows a check box instead of the more common edit control. The DataGridView control, in the absence of any other information, will read the schema from the data source (which in this case is the Products table), and infer from the column types what control is to be displayed. Unlike the original DataGrid control, the DataGridView control has built-in support for image columns, buttons, and combo boxes.

The data in the database does not change when fields are altered in the data grid, because the data is stored only locally on the client computer - there is no active connection to the database. Updating data in the database is discussed later in this chapter.

Displaying Data from a DataView

A DataView provides a means to filter and sort data within a DataTable. When data has been selected from the database, it is common to permit the user to sort that data, for example, by clicking on column headings. In addition, the user might want to filter the data to show only certain rows, such as all those that have been altered. A DataView can be filtered so that only selected rows are shown to the user; however, you cannot filter the columns from the DataTable.

Important 

A DataView does not permit the filtering of columns, only rows.

An example of how to limit the columns shown is provided in the section “DataGridTableStyle and DataGridColumnStyle” later in this chapter.

To create a DataView based on an existing DataTable, use the following code:

  DataView dv = new DataView(dataTable); 

Once created, further settings can be altered on the DataView, which affect the data and operations permitted on that data when it is displayed within the data grid. For example:

  • Setting AllowEdit = false disables all column edit functionality for rows.

  • Setting AllowNew = false disables the new row functionality.

  • Setting AllowDelete = false disables the delete row capability.

  • Setting the RowStateFilter displays only rows of a given state.

  • Setting the RowFilter enables you to filter rows.

The next section explains how to use the RowStateFilter setting; the other options are fairly self-explanatory.

Filtering Rows by Data

After the DataView has been created, the data displayed by that view can be altered by setting the RowFilter property. This property, typed as a string, is used as a means of filtering based on certain criteria defined by the value of the string. Its syntax is similar to a WHERE clause in regular SQL, but it is issued against data already selected from the database.

The following table shows some examples of filter clauses.

Open table as spreadsheet

Clause

Description

UnitsInStock > 50

Shows only those rows where the UnitsInStock column is greater than 50.

Client = ‘Smith’

Returns only the records for a given client.

County LIKE ‘C*‘

Returns all records where the County field begins with a C - in this example, the rows for Cornwall, Cumbria, Cheshire, and Cambridgeshire would be returned. The % character can be used as a single-character wildcard, whereas the * denotes a general wildcard that will match zero or more characters.

The runtime will do its best to coerce the data types used within the filter expression into the appropriate types for the source columns. For instance, it is perfectly legal to write “UnitsInStock > ‘50’“ in the earlier example, even though the column is an integer. If an invalid filter string is provided, an EvaluateException will be thrown.

Filtering Rows on State

Each row within a DataView has a defined row state, which has one of the values shown in the following table. This state can also be used to filter the rows viewed by the user.

Open table as spreadsheet

DataViewRowState

Description

Added

Lists all rows that have been newly created.

CurrentRows

Lists all rows except those that have been deleted.

Deleted

Lists all rows that were originally selected and have been deleted; does not show newly created rows that have been deleted.

ModifiedCurrent

Lists all rows that have been modified and shows the current value of each column.

ModifiedOriginal

Lists all rows that have been modified but shows the original value of the column and not the current value.

OriginalRows

Lists all rows that were originally selected from a data source. Does not include new rows. Shows the original values of the columns (that is, not the current values if changes have been made).

Unchanged

Lists all rows that have not changed in any way.

Figure 29-5 shows a grid that can have rows added, deleted, or amended, and a second grid that lists rows in one of the preceding states.

image from book
Figure 29-5

The filter not only applies to the visible rows but also to the state of the columns within those rows. This is evident when choosing the ModifiedOriginal or ModifiedCurrent selections. These states are described in Chapter 19, “.NET Security,” and are based on the DataRowVersion enumeration. For example, when the user has updated a column in the row, the row will be displayed when either ModifiedOriginal or ModifiedCurrent is chosen; however, the actual value will be either the Original value selected from the database (if ModifiedOriginal is chosen) or the current value in the DataColumn (if ModifiedCurrent is chosen).

Sorting Rows

Apart from filtering data, you might also have to sort the data within a DataView. To sort data in ascending or descending order, simply click the column header in the DataGridView control (see Figure 29-6). The only trouble is that the control can sort by only one column, whereas the underlying DataView control can sort by multiple columns.

image from book
Figure 29-6

When a column is sorted, either by clicking the header (as shown on the ProductName column) or in code, the DataGrid displays an arrow bitmap to indicate which column the sort has been applied to.

To set the sort order on a column programmatically, use the Sort property of the DataView:

  dataView.Sort = "ProductName"; dataView.Sort = "ProductName ASC, ProductID DESC"; 

The first line sorts the data based on the ProductName column, as shown in Figure 24-6. The second line sorts the data in ascending order, based on the ProductName column, then in descending order of ProductID.

The DataView supports both ascending (default) and descending sort orders on columns. If more than one column is sorted in code in the DataView, the DataGridView will cease to display any sort arrows.

Each column in the grid can be strongly typed, so its sort order is not based on the string representation of the column but instead is based on the data within that column. The upshot is that if there is a date column in the DataGrid, the user can sort numerically on the date rather than on the date string representation.

Displaying Data from a DataSet Class

There is one feature of DataSets that the DataGridView cannot match the DataGrid in - this is where a DataSet is defined that includes relationships between tables. As with the preceding DataGridView examples, the DataGrid can only display a single DataTable at a time. However, as shown in the following example, DataSourceDataSet, it is possible to navigate relationships within the DataSet on-screen. The following code can be used to generate such a DataSet based on the Customers and Orders tables in the Northwind database. This example loads data from these two DataTables and then creates a relationship between these tables called CustomerOrders:

  string orders = "SELECT * FROM Orders"; string customers = "SELECT * FROM Customers"; SqlConnection conn = new SqlConnection(source); SqlDataAdapter da = new SqlDataAdapter(orders, conn); DataSet ds = new DataSet(); da.Fill(ds, "Orders"); da = new SqlDataAdapter(customers , conn); da.Fill(ds, "Customers"); ds.Relations.Add("CustomerOrders",                   ds.Tables["Customers"].Columns["CustomerID"],                   ds.Tables["Orders"].Columns["CustomerID"]); 

Once created, the data in the DataSet is bound to the DataGrid simply by calling SetDataBinding():

  dataGrid1.SetDataBinding(ds, "Customers"); 

This produces the output shown in Figure 29-7.

image from book
Figure 29-7

Unlike the DataGridView examples shown in this chapter, there is now a + sign to the left of each record. This reflects the fact that the DataSet has a navigable relationship between customers and orders. Any number of such relationships can be defined in code.

When the user clicks the + sign, the list of relationships is shown (or hidden if already visible). Clicking the name of the relationship enables you to navigate to the linked records (see Figure 24-8), in this example, listing all orders placed by the selected customer.

image from book
Figure 29-8

The DataGrid control also includes a couple of new icons in the top-right corner. The arrow permits the user to navigate to the parent row, and will change the display to that on the previous page. The header row showing details of the parent record can be shown or hidden by clicking the other button.

Displaying Data in a DataViewManager

The display of data in a DataViewManager is the same as that for the DataSet shown in the previous section. However, when a DataViewManager is created for a DataSet, an individual DataView is created for each DataTable, which then permits the code to alter the displayed rows based on a filter or the row state as shown in the DataView example. Even if the code doesn’t need to filter data, it is good practice to wrap the DataSet in a DataViewManager for display, because it provides more options when revising the source code.

The following creates a DataViewManager based on the DataSet from the previous example and then alters the DataView for the Customer table to show only customers from the United Kingdom:

  DataViewManager dvm = new DataViewManager(ds); dvm.DataViewSettings["Customers"].RowFilter = "Country='UK'"; dataGrid.SetDataBinding(dvm, "Customers"); 

Figure 29-9 shows the output of the DataSourceDataViewManager sample code.

image from book
Figure 29-9

IListSource and IList Interfaces

The DataGridView also supports any object that exposes one of the interfaces IListSource or IList. IListSource only has one method, GetList(), which returns an IList interface. IList, on the other hand, is somewhat more interesting and is implemented by a large number of classes in the runtime. Some of the classes that implement this interface are Array, ArrayList, and StringCollection.

When using IList, the same caveat for the object within the collection holds true as for the Array implementation shown earlier - if a StringCollection is used as the data source for the DataGrid, the length of the strings is displayed within the grid, not within the text of the item as expected.

Displaying Generic Collections

In addition to the types already described, the DataGridView also supports binding to generic collections. The syntax is just as in the other examples already provided in this chapter - simply set the DataSource property to the collection, and the control will generate an appropriate display.

Once again, the columns displayed are based on the properties of the object - all public readable fields are displayed in the DataGridView. The following example shows the display for a list class defined as follows:

  class PersonList : List < Person > { } class Person {     public Person( string name, Sex sex, DateTime dob )     {         _name = name;         _sex = sex;         _dateOfBirth = dob;     }     public string Name     {         get { return _name; }         set { _name = value; }     }     public Sex Sex     {         get { return _sex; }         set { _sex = value; }     }     public DateTime DateOfBirth     {         get { return _dateOfBirth; }         set { _dateOfBirth = value; }     }     private string _name;     private Sex _sex;     private DateTime _dateOfBirth; } enum Sex {     Male,     Female } 

The display shows several instances of the Person class that were constructed within the PersonList class. See Figure 29-10.

image from book
Figure 29-10

In some circumstances, it might be necessary to hide certain properties from the grid display - for this you can utilize the Browsable attribute as shown in the following code snippet. Any properties marked as non-browsable are not displayed in the property grid.

  [Browsable(false)] public bool IsEmployed {    ... } 

The DataGridView uses this property to determine whether to display the property or hide it. In the absence of the attribute, the default is to display the property. If a property is read-only, the grid control will display the values from the object, but it will be read-only within the grid.

Any changes made in the grid view are reflected in the underlying objects - so for example, if in the previous code the name of a person was changed within the user interface, the setter method for that property would be called.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net