12.3. The DataGridView Class

 < Day Day Up > 

The DataGridView control, introduced with .NET 2.0, supersedes the DataGrid which now exists primarily for legacy purposes. With more than a hundred properties and methods, the DataGridView is by far the most complex Windows Forms control for displaying data. Accordingly, it is also the most flexible. Styles that govern appearance can be applied on a cell-by-cell basis, by rows, by columns, or across all cells in the grid. Cells are not limited to text. They may contain a TextBox, Image, CheckBox, Link, or Button control.

Data binding is supported by the DataSource property, just as with the controls defined in the previous section. In addition, the DataGridView provides a unique virtual mode that permits it to handle more than 100,000 rows of data. DataGridView methods, events, and properties allow an application to easily manage the mapping between virtual and physical storage.

All of these features are discussed in this section. We'll look at selected properties and events along with code examples that illustrate their use.

Properties

Despite its myriad features, the DataGridView has an elegantly simple structure. As shown in Figure 12-6, in its most elemental form, it consists of column headers, row headers, and cells. To these, we can add the Columns and Rows collections that allow an application to access the grid by indexing a row or column. That is the foundation. Each property and event discussed in this section relates to one of these five classes.

Figure 12-6. Basic DataGridView elements


The DataGridView class inherits many of its properties from the Control class; to these, it adds properties required to support its own special characteristics and behavior. The properties listed in Table 12-1 are primarily in this latter category. The list is not meant to be exhaustive; instead, it presents those properties you'll refer to most frequently when implementing a grid.

Table 12-1. Selected Properties of the DataGridView Class

Category

Property Name

Description

User functionality

 AllowUserToAddRows AllowUserToDeleteRows 

Indicates whether a user may add/delete rows. Default: true.

 

AllowUserToOrderColumns

Indicates whether user can rearrange columns.

 

ColumnHeadersHeightResizable

Indicates whether the user can change the height of the column headers. Default: TRue.

 

MultiSelect

Indicates whether user may select one or more rows at a time.

 

SelectionMode

Indicates the cells selected when clicking any individual or header cell.

enum DataGridViewSelectionMode values:

 ColumnHeaderSelect RowHeaderSelect FullColumnSelect FullRowSelect CellSelect 

 

ReadOnly

Indicates whether user can modify data in cells. Values: TRue or false.

Appearance

AlternatingRowsDefaultCellStyle

Gets or sets the default cell style applied to the odd numbered rows in the grid.

 

BackColor

The background color of the grid.

 

BackgroundColor

Gets or sets the background color for the area of the grid not containing data cells or column/row headers.

 

BorderStyle

Gets or sets the border style for the DataGridView.

enum BorderStyle values:

 BorderStyle.Fixed3D BorderStyle.FixedSingle BorderStyle.None 

 

CellBorderStyle

Gets or sets the border style used for cells in the grid.

enum DataGridViewCellBorderStyle values:

(to draw between rows)

 SingleHorizontal SunkenHorizontal RaisedHorizontal 

(to draw between columns)

 SingleVertical SunkenVertical RaisedVertical 

(to place a border between rows and columns)

 SingleSunken Raised 

 

ColumnCount

Gets or sets the number of columns in the DataGridView.

 
 ColumnHeadersBorderStyle RowHeadersBorderStye 

Border style applied to to column/row headers.

enum DataGridViewHeaderBorderStyle values:

 Custom     Raised     Sunk None       Single 

 
 ColumnHeadersVisible RowHeadersVisible 

Displays or suppresses headers. Values: true or false.

 

ColumnHeaderDefaultCellStyle

Defines cell style properties for column header cells.

 

DefaultCellStyle

DataGridViewCellStyle object that defines the default cell style properties for cells. Note that this includes column header cells.

 

FirstDisplayedCell

The first cell displayed in the grid, usually upper-left corner.

 

GridColor

The color of the lines separating the cells.

Collections

Columns

Collection of all grid columns. Individual columns are accessed by an index: Columns[iI].

 

Rows

Collection of all grid rows. Individual rows are accessed by an index: Rows[i].

 

SelectedColumns

Collection of columns selected.

 

SelectedRows

Collection of rows selected.

 

SelectedCells

Collection of cells selected.


Constructing a DataGridView

Listing 12-6 shows how to define columns for a DataGridView, set properties to define its appearance and behavior, and add rows of data. (We'll see in the succeeding example how to use the more common approach of loading data from a database.)

Note that the column header cells and data cells have different styles. If a style is not set for the header, it uses the same DefaultCellStyle as the data cells.

Listing 12-6. Setting DataGridView Properties and Adding Rows of Data
 // Set properties of a DataGridView and fill with data private void CreateGrid() {    // (1) Define column headers    dataGridView1.ColumnCount = 3;    dataGridView1.Columns[0].HeaderText = "Movie Title";    dataGridView1.Columns[1].HeaderText = "Year";    dataGridView1.Columns[2].HeaderText = "Director";    dataGridView1.Columns[1].Name  = "Year";    dataGridView1.Columns[0].Width = 150;    dataGridView1.Columns[1].Width = 40;    dataGridView1.Columns[2].Width = 110;    // (2) Define style for data cells    DataGridViewCellStyle style = new DataGridViewCellStyle();    style.BackColor = Color.Bisque;    style.Font = new Font("Arial", 8, FontStyle.Bold);    style.ForeColor = Color.Navy;    //                         (left,top,right,bottom)    style.Padding = new Padding(5, 2, 5, 5);    style.SelectionBackColor = Color.LightBlue;    dataGridView1.DefaultCellStyle = style;    // (3) Define style for column headers    DataGridViewCellStyle styleHdr = new           DataGridViewCellStyle();    styleHdr.Padding = new Padding(1, 1, 1, 1);    styleHdr.BackColor = Color.OldLace;    styleHdr.ForeColor = Color.Black;    dataGridView1.ColumnHeadersDefaultCellStyle = styleHdr;    // (4) Define user capabilities    dataGridView1.AllowUserToAddRows      = false;    dataGridView1.AllowUserToOrderColumns = false;    dataGridView1.AllowUserToDeleteRows   = false;    // (5) Place data in grid manually (datasource is better)    object[] row1 = {"Casablanca", "1942","Michael Curtiz"};    dataGridView1.Rows.Add(row1);    object[] row2 = {"Raging Bull","1980","Martin Scorsese"};    dataGridView1.Rows.Add(row2);    object[] row3 = {"On the Waterfront","1954","Elia Kazan"};    dataGridView1.Rows.Add(row3);    object[] row4 = {"Some Like it Hot","1959","Billy Wilder"};    dataGridView1.Rows.Add(row4); } 

Figure 12-7 shows the DataGridView created by this code.

Figure 12-7. DataGridView built from code in Listing 12-6


DataBinding with a DataGridView

A DataGridView is bound to a data source using complex binding. As in our list box example, the DataSource property specifies the data source. The similarity ends there, however, because a DataGridView must display multiple data values. To do so, the DataMember property is set to the name of a table within the data source. The data to be displayed in each column is specified by setting the column's DataPropertyName property to the name of the underlying data table column.

 // Turn this off so column names do not come from data source dataGridView1.AutoGenerateColumns = false; // Specify table as data source dataGridView1.DataSource = ds;        // Dataset dataGridView1.DataMember = "movies";  // Table in dataset // Tie the columns in the grid to column names in the data table dataGridView1.Columns[0].DataPropertyName = "Title"; dataGridView1.Columns[1].DataPropertyName = "Year"; dataGridView1.Columns[2].DataPropertyName = "director"; 

The DataGridView supports two-way data binding for ADO.NET data sources: Changes made to the grid are reflected in the underlying table, and changes made to the table are reflected in the grid. For example, this code responds to a button click by adding a new row to the grid's data source. The addition is immediately reflected in the control. However, if we try to add a row directly to the DataGridView, an exception occurs because adding directly to a bound control is not permitted.

 private void buttonAdd_Click(object sender, EventArgs e) {    // Adding to underlying table is okay    r[0] = "TAXI";    r[1] = "1976";    r[2] = "Martin Scorsese";    dt.Rows.Add(r);    // Adding directly to DataGridView does not work    object[] row = {"The Third Man", "1949", "Orson Welles"};    DataRow r = dt.NewRow();    DataGridView1.Rows.Add(row4);  // Fails! } 

Updating the original database from which a grid is loaded can be done by issuing individual SQL commands or using a DataAdapter. The discussion in the previous section applies.

Core Note

A DataGridView may have a mixture of bound and non-bound columns. Thus, columns can be added to a bound control, but rows cannot.


Setting the Row Height

The default height of rows in a DataGridView is based on accommodating a single line of text. If the row contains large sized fonts or images, they are truncated. It is usually better to force the grid to take the size of each cell in the row into account and base the overall height on the tallest cell. That's the role of the grid's AutoSizeRows method. Its simplest overloaded version takes a single parameter a DataGridViewAutoSizeRowsMode enumeration value that indicates the criterion used for setting row height. The two most useful enumeration members are ColumnAllRows, which bases the row height on all columns in the row, and ColumnsDisplayedRows, which applies the same criterion, but to visible rows only.

 dataGridView1.AutoSizeRows(       DataGridViewAutoSizeRowsMode.ColumnsAllRows); 

The AutoSizeRows method sets the row size when it is executed. If subsequent updates cause the height of cells in a row to change, the row height does not adjust to the changes. Also, if a row is sortable, clicking a column header to sort the grid causes all rows to revert to the default row height. Fortunately, the DataGridView has an AutoSizeRowsMode property that causes row heights to automatically adjust to changes in grid content.

 dataGridView1.AutoSizeRowsMode =       DataGridViewAutoSizeRowsMode.HeaderAndColumnsAllRows; 

Note that this statement does not take effect until the AutoSizeRows method is executed, and that it prevents users from manually resizing rows.

Working with Columns and Column Types

The DataGridView is not a full-blown spreadsheet, but it does offer some features a user expects from a spreadsheet. These include the following:

  • Frozen Column(s). For a grid that requires horizontal scrolling, it is often useful to "freeze" columns so that they always remain on the screen. Setting a column's Frozen property to true has the effect of freezing it and all columns to its left.

      dataGridView1.Columns[0].Frozen = true; 

  • ReadOnly Columns. Selected column can be made read-only.

      dataGridView1.Columns[2].ReadOnly = true; 

  • Minimum Width. By default, a user can widen and narrow columns in a grid. The minimum size permitted for a column can be controlled by setting the MinimumWidth property to a value in pixels:

      dataGridView1.Columns[0].MinimumWidth=100; 

  • Sorting. By default, clicking a column header sorts the rows based on values in that column if the column contains sortable values. It's SortMode property can be used to disable sorting:

      dataGridView1.Columns[0].SortMode =                DataGridViewColumnSortMode.NotSortable; 

  • Multiple Column Types. Six predefined column classes are available that can be used to represent information in a grid, using the familiar formats of the TextBox, CheckBox, Image, Button, ComboBox, and Link. The name for each of these controls follows the format DataGridViewControlnameColumn.

    This code segment adds a column of buttons to a grid. The first step is to create an instance of the column class. Its characteristics and data values if any are then set. Finally, the Columns.Add method is used to add the column to the grid's column collection.

     // (1) Create instance of column type DataGridViewButtonColumn buttons = new        DataGridViewButtonColumn(); // Text to place in column header buttons.HeaderText = "Delete"; // (2) Set characteristics of control buttons.Text = "Delete";   // Default text for button buttons.FlatStyle = FlatStyle.Standard; // Create a datagridview cell to use as a template to set // all buttons in the column to the same style. buttons.CellTemplate = new DataGridViewButtonCell(); buttons.CellTemplate.Style.BackColor = Color.Yellow ; buttons.CellTemplate.Style.Font = new Font("Arial", 8); // Specify column position on grid buttons.DisplayIndex = 1; // (3) Add column to grid dataGridView.Columns.Add(buttons); 

Any of the column types may be bound to a data source. Although a button is usually set manually, it can be bound to a property in the grid's data source in two ways:

 // Use the DataGridviewButtonColumn class buttons.DataPropertyName = "Title"; // Use the Columns class (button is in column 1 of the grid) dataGridView3.Columns[1].DataPropertyName = "Title"; 

Buttons provide a convenient way for a user to select a grid row and trigger an action such as a pop-up form that displays further information related to the row. Buttons located in grid cells, however, have no direct event, such as a Click, associated with them. Instead, events are associated with an action on the overall grid or specific cells on the grid. By identifying a cell for instance, an event handler can determine which button is clicked.

Events

Just about every mouse and cursor movement that can occur over a DataGridView can be detected by one of its events. In addition, events signify when data is changed, added, or deleted. Table 12-2 provides a summary of the most useful events. Accompanying the table is a list of the delegate used to implement these events. (See Appendix B for a complete list of events.)

Table 12-2. Selected DataGridView Events

Category

Event (Delegate)

Description

Cell actions

CellValueChanged (1)

Occurs when the value of a cell changes.

 

CurrentCellChanged (3)

Occurs when the value of the current cell changes

 

CellClick (1)

Occurs when any part of the cell is clicked. This includes cell borders and padding.

 

CellContentClick (1)

Occurs only if the cell content is clicked.

 

CellEnter (1)

CellLeave (1)

Occurs when cell receives/loses input focus.

 

CellFormatting (5)

Occurs prior to formatting a cell for display.

 

CellMouseClick (2)

CellMouseDoubleClick (2)

Occurs whenever a mouse clicks/double clicks anywhere on a cell.

 

CellMouseDown (2)

CellMouseUp (2)

Occurs when a mouse button is pressed/raised while it is over a cell

 

CellMouseEnter (1)

CellMouseLeave (1)

Occurs when the mouse pointer enters or leaves a cell's area.

 

CellPainting (6)

Raised when a cell is to be painted.

Column actions

ColumnHeaderMouseClick (2)

ColumnHeaderMouseDouble-Click (2)

Occurs when a column header is clicked/double clicked.

Row actions

RowEnter (1)

RowLeave (1)

Occurs when a row receives/loses the input focus.

 

RowHeaderMouseClick (2)

RowHeaderDoubleMouse-Click (2)

Occurs when a user clicks/double clicks a row header

 

UserAddedRow (4)

UserDeletedRow (4)

Occurs when a user adds/deletes a row in the grid.

Data error

DataError (7)

Occurs when an external data parsing or validation operations fails. Typically occurs due to an attempt to load invalid data into a data grid cell.


The following are delegates associated with events in Table 12-2:

 (1) public sealed delegate void DataGridViewCellEventHandler(       object sender, DataGridViewCellEventArgs e) (2) public sealed delegate void DataGridViewCellM_useEventHandler(       object sender, DataGridViewCellMouseEventArgs e) (3) public sealed delegate void EventHandler(       object sender, EventHandlerArgs e) (4) public sealed delegate void DataGridViewRowEventHandler (       object sender, DataGridViewRowEventArgs e) (5) public sealed delegate void        DataGridViewCellFormattingEventHandler(          object sender, DataGridViewCellFormattingEventArgs e) (6) public sealed delegate void        DataGridViewCellPaintingEventHandler(          object sender, DataGridViewCellPaintingEventArgs e) (7) public sealed delegate void       DataGridViewDataErrorEventHandler(          object sender, DataGridViewDataErrorEventArgs e) 

Let's look at some common uses for these events.

Cell Formatting

The CellFormatting event gives you the opportunity to format a cell before it is rendered. This comes in handy if you want to distinguish a subset of cells by some criteria. For example, the grid in Figure 12-7 contains a column indicating the year a movie was released. Let's change the background color of cells in that column to red if the year is less than 1950.

 // Set cells in year column to red if year is less than 1950 private void Grid3_CellFormatting(object sender,         DataGridViewCellFormattingEventArgs e) {    if (this.dataGridView3.Columns[e.ColumnIndex].Name == "Year")    {       string yr = (string)e.Value;       if (Int32.Parse(yr) < 1950)       {           e.CellStyle.ForeColor = Color.Red;           e.CellStyle.SelectionForeColor = Color.Red;           // Indicate that event was handled           e.FormattingApplied = true;       }    } } 

The ColumnIndex property of the EventArgs parameter is used to determine if the year column is being formatted. If so, the code checks the year and formats the cell accordingly. Note that the FormattingApplied property must be set if custom formatting is performed.

Recognizing Selected Rows, Columns, and Cells

As shown in Table 12-2, selecting a cell in a grid can trigger any number of events that can be used to indicate the current cell or the cell just left. Some of the events are almost over-engineered. For example, there seems little to distinguish CellContentClick and CellClick. Others exist to recognize grid navigation using both the mouse and keyboard: The CellClick is not triggered by arrow keys; however, the CellEnter event is fired no matter how a cell is selected. All of these cell-related events have a consistent event handler signature. The EventArgs parameter provides column and row index properties to identify the cell. Here is an example:

 private void Grid1_CellEnter(object sender,       DataGridViewCellEventArgs e) {    // Both of these display the column index of the selected cell    MessageBox.Show("enter "+e.ColumnIndex.ToString());    MessageBox.Show(           DataGridView1.CurrentCell.ColumnIndex.ToString()); } 

Core Note

Although row and column header cells cannot become "current cells," they are assigned a column and row index value. Row headers always have a column index of 1, and column headers have row index of 1.


The cell events can be used to recognize a single row and column selection. However, a grid may also permit multiple row, column, and cell selections. In these cases, it is necessary to use the SelectedRows, SelectedColumns, and SelectedCells collections to access the selected grid values.

Multiple row selection is made available on a DataGridView by setting its MultiSelect property to TRue which is the default value. A row is selected by clicking its row header. It can also be selected by clicking any cell in the row if the grid's SelectionMode property is set to DataGridViewSelectionMode.FullRowSelect. The property can also be set to FullColumnSelect, which causes a cell's column to be selected. Note that column and row selection are mutually exclusive: only one can be in effect at a time.

This segment illustrates how to iterate through the collection of selected rows. The same approach is used for columns and cells.

 // Display selected row numbers and content of its column 1 if (dataGridView1.SelectedRows.Count > 0) {    StringBuilder sb = new StringBuilder();    for (int i = 0; i < dataGridView1.SelectedRows.Count; i++)    {       sb.Append("Row: ");           sb.Append(             dataGridView1.SelectedRows[i].Index.ToString() );       sb.Append( dataGridView1.SelectedRows[i].Cells[1].Value);       sb.Append(Environment.NewLine);    }    MessageBox.Show (sb.ToString(), "Selected Rows"); } 

Data Error Handling

The DataError event fires when a problem occurs loading data into a grid or posting data from the grid to the underlying data store. The error is quite easy to detect: compare the value of the Context property of the ErrorEventArgs parameter with the DataGridViewDataErrorContext enumeration values. Here is an example:

 // Define event handler DataGridView1.DataError += new         DataGridViewDataErrorEventHandler(DataGridView1_DataError); // DataError Event Handler private void dataGridView1_DataError(object sender,         DataGridViewDataErrorEventArgs dgError) {    // Context provides information about the grid when the    // error occurred.    MessageBox.Show("Error: " + dgError.Context.ToString());    // Problem committing grid data to underlying data source    if (dgError.Context == DataGridViewDataErrorContext.Commit)    {       MessageBox.Show("Commit error");    }    // Occurs when selection cursor moves to another cell    if (dgError.Context ==           DataGridViewDataErrorContext.CurrentCellChange)    {       MessageBox.Show("Cell change");    }    if (dgError.Context ==           DataGridViewDataErrorContext.Parsing)    {       MessageBox.Show("parsing error");    }    // Could not format data coming from/going to data source    if (dgError.Context ==           DataGridViewDataErrorContext.Formatting)    {       MessageBox.Show("formatting error");    } } 

Setting Up Master-Detail DataGridViews

One of the more common relationships between tables in a database is that of the master-detail relationship, where the records in the master table have multiple associated records in the detail table. DataGridViews provide a natural way of displaying this relationship. To illustrate, let's create an application based on the Films database that displays a master grid containing a list of movies and a detail grid that display actors who played in the movie selected in the first grid. To make it interesting, we'll include an image column in the movie grid that contains a picture of the Oscar statuette for movies that won for best picture.

The master grid is bound to the movies table; the details grid is bound to the actors table. Both tables, as shown in Figure 12-8, contain the columns that are bound to their respective DataGridView columns. In addition, they contain a movieID column that links the two in the master-detail relationship.

Figure 12-8. Master-detail tables


The tables and their relationships are created using the techniques described in Chapter 11:

 ds = new DataSet(); DataTable dt = new DataTable("movies");  // Master DataTable da = new DataTable("actors");  // Detail da.Columns.Add("movieID"); da.Columns.Add("firstname"); da.Columns.Add("lastname"); // dt.Columns.Add("movieID"); dt.Columns.Add("Title"); dt.Columns.Add("Year"); dt.Columns.Add("picture", typeof(Bitmap));  // To hold image ds.Tables.Add(dt); ds.Tables.Add(da); // Define master-detail relationship DataRelation rel = new DataRelation("movieactor",        dt.Columns["movieID"], da.Columns["movieID"]); ds.Relations.Add(rel); 

After defining the table schemas, they are populated from the database using a DataReader object. Because the database does not contain an image although it could the image is inserted based on the value of the bestPicture field.

 Bitmap oscar   = new Bitmap(@"c:\oscar.gif");   // Oscar image Bitmap nooscar = new Bitmap(@"c:\nooscar.gif"); // Blank image // Populate movies table from datareader while (dr.Read()) {    DataRow drow = dt.NewRow();    drow["Title"] = (string)(dr["movie_Title"]);    drow["Year"]  = ((int)dr["movie_Year"]).ToString();    drow["movieID"] = (int)dr["movie_ID"];    if ((string)dr["bestPicture"] == "Y") drow["picture"] =           oscar; else drow["picture"] = nooscar;    dt.Rows.Add(drow); } 

The actors table is filled with the results of the query:

 sql = "SELECT am.movie_ID, actor_first,actor_last FROM actors a       JOIN actor_movie am ON a.actor_ID = am.actor_ID"; 

After the tables are created and populated, the final steps are to define the grids and bind their columns to the tables. This segment adds three columns to the master grid one of which is an image type column.

 DataGridViewImageColumn vic = new DataGridViewImageColumn(); dataGridView1.Columns.Add(vic);   // Add image type column // dataGridView1.ColumnCount = 3; dataGridView1.Columns[0].Name = "Oscar"; dataGridView1.Columns[1].HeaderText = "Movie Title"; dataGridView1.Columns[2].HeaderText = "Year"; 

Then, the binding is performed:

 // Bind grids to dataset dataGridView1.DataSource = ds; dataGridView1.DataMember = "movies"; dataGridView2.DataSource = ds; // ***Set to DataRelation for detail  dataGridView2.DataMember = dt.TableName+".movieactor"; // Bind grid columns to table columns dataGridView1.Columns[0].DataPropertyName = "picture"; dataGridView1.Columns[1].DataPropertyName = "Title"; dataGridView1.Columns[2].DataPropertyName = "Year"; dataGridView1.Columns[3].DataPropertyName = "director";  dataGridView2.Columns[0].DataPropertyName = "firstname"; dataGridView2.Columns[1].DataPropertyName = "lastname"; 

Pay close attention to the binding of dataGridView2. It is bound to the relationship defined between the tables, rather than directly to the actors table. This binding causes the names of the movie's cast to be displayed in the grid when a movie is selected.

Figure 12-9 shows a sample screen. Much of the excluded code in this example deals with setting grid styles and capabilities. A full code listing is available in the book's code download. (See the Preface for the download URL addresses and instructions.)

Figure 12-9. Master-detail relationship


Virtual Mode

When a DataGridView is bound to a data source, the entire data source must exist in memory. This enables quick refreshing of the control's cells as a user navigates from row to row. The downside is that a large data store may have prohibitive memory requirements. To handle excessive memory requirements, a DataGridView can be run in virtual mode by setting its VirtualMode property to TRue. In this mode, the application takes responsibility for maintaining an underlying data cache to handle the population, editing, and deletion of DataGridView cells based on actions of the user. The cache contains data for a selected portion of the grid. If a row in the grid cannot be satisfied from cache, the application must load the cache with the necessary data from the original data source. Figure 12-10 compares virtual storage with binding to a DataTable.

Figure 12-10. Data binding versus virtual mode


Virtual mode implementation requires that an application handle two special virtual mode events: CellValueNeeded, which occurs when a cell value must be displayed; and CellValuePushed, which occurs when a cell's value is edited. Other events are also required to manage the data cache. These are summarized in Table 12-3.

Table 12-3. DataGridView Events Used to Implement Virtual Mode

Event

Description

NewRowsNeeded

Virtual mode event. Occurs when a row is appended to the DataGridView.

CellValueNeeded

Virtual mode event. Occurs when cell in grid needs to be displayed.

CellValuePushed

Virtual mode event. Occurs when a cell value is edited by the user.

RowValidated

Occurs when another row is selected.

UserDeletingRow

Occurs when a row is selected and the Delete key is pressed.


To illustrate the fundamentals of implementing a DataGridView in virtual mode, let's look at the code used to create the DataGridView shown in Figure 12-11.

Figure 12-11. DataGridView using virtual mode


The variables having class scope are shown here. Note that the data cache is implemented as a generics List object that holds instances of the movie class. The movie class exposes three properties that are displayed on the grid: Title, Movie_Year, and Director.

 DataGridView dgv; List<movie> movieList = new List<movie>(20);  // cache bool rowNeeded;   // True when new row appended to grid   int storeRow = 0; int currRow = -1; // Set to row being added movie currMovie;  // Holds movie object for current row 

Listing 12-7 shows the overhead code to initialize the DataGridView, register the event handlers, and populate the data cache (this would usually come from a database).

Listing 12-7. Virtual DataGridView: Initialization
 // Set properties of a DataGridView and fill with data dgv = new DataGridView(); // Event handlers for virtual mode events dgv.CellValueNeeded += new       DataGridViewCellValueEventHandler(CellNeeded); dgv.CellValuePushed += new        DataGridViewCellValueEventHandler(CellPushed); dgv.NewRowNeeded += new        DataGridViewRowEventHandler(RowNeeded); // Event handlers always available for DataGridView dgv.UserDeletingRow += new        DataGridViewRowCancelEventHandler (RowDeleting); dgv.RowValidated += new        DataGridViewCellEventHandler( RowValidated); dgv.VirtualMode = true; dgv.RowCount = 5; dgv.ColumnCount = 3; // Headers for columns dgv.Columns[0].HeaderText = "title"; dgv.Columns[1].HeaderText = "year"; dgv.Columns[2].HeaderText = "director"; // Fill cache. In production, this would come from database. movieList.Add(new movie("Citizen Kane",1941,"Orson Welles")); movieList.Add(new movie("The Lady Eve",1941,"                         "Preston Sturges")); // ... Add other movies here 

The heart of the application is represented by the event handler methods shown in Listing 12-8. To summarize them:

  • RowNeeded. Is triggered when the user begins to add a new row at the bottom of the grid. currRow is set to the row number of any row being added.

  • CellNeeded. Is triggered when a cell needs to be redrawn. This does not require that a row be selected, but occurs as you move the cursor over cells in the grid. This routine identifies the column the cell is in and displays the data from the cache or the object that is created for new rows. Note that the MapRow() is called to translate a row in the grid to its corresponding row in the cache. In this simple example, there is always a one-to-one relationship because the cache and grid contain the same number of rows. In a production application, row 5000 in a grid might map to row 1 in the cache.

  • CellPushed. Called when a cell value is edited. This routine updates a movie object that represents the selected row with the new value.

  • RowValidated. Signals that a different row has been selected and is used to update the previous row. If the row exists in the cache, it is updated; a new row is added to the cache.

  • RowDeleting. Called when user selects a row to delete. If the row exists in the cache, it is removed.

Listing 12-8. Virtual DataGridView: Event Handlers
 // Called when a new row is appended to grid private void RowNeeded(object sender,                         DataGridViewRowEventArgs e) {    rowNeeded = true;    currRow = dgv.Rows.Count - 1;  } // Called when a cell must be displayed/refreshed private void CellNeeded(object sender,                          DataGridViewCellValueEventArgs e) {    if (rowNeeded)    {       rowNeeded = false;       currMovie = new movie();       return;    }     storeRow = MapRow(e.RowIndex);    if(storeRow >=0 && currRow  ==-1)           currMovie =  movieList[storeRow];    string colName = dgv.Columns[e.ColumnIndex].HeaderText;    if(storeRow>=0)  // Refresh cell from cache    {       if (colName == "title")e.Value =             movieList[storeRow].Title;       if (colName == "year") e.Value =              movieList[storeRow].Movie_Year.ToString();       if (colName == "director") e.Value =              movieList[storeRow].Director;    } else           // refresh cell from object for new row    {       if (colName == "title")e.Value = currMovie.Title;       if (colName == "year")e.Value =              currMovie.Movie_Year.ToString();       if (colName == "director") e.Value = currMovie.Director;    } } // Cell has been updated private void CellPushed(object sender,                          DataGridViewCellValueEventArgs e) {    // Update property on movie object for this row    storeRow = MapRow(e.RowIndex);    string colName = dgv.Columns[e.ColumnIndex].HeaderText;    if (colName == "title") currMovie.Title = (string)e.Value;    if (colName == "year")    {       int retval;       if(int.TryParse((string)e.Value,out retval))              currMovie.Movie_Year = retval;    }    if (colName == "director") currMovie.Director =           (string)e.Value; }  // Occurs when user changes current row // Update previous row in cache when this occurs private void RowValidated(object sender,                           DataGridViewCellEventArgs e) {    storeRow = MapRow(e.RowIndex);    if (storeRow < 0) storeRow = movieList.Count;    currRow = -1;    if (currMovie != null)    {       // Save the modified Customer object in the data store.       storeRow = MapRow(e.RowIndex);       if (storeRow >= 0)             movieList[storeRow] = currMovie;       else movieList.Add(currMovie);          currMovie = null;    } } // Row selected and Del key pushed private void RowDeleting(object sender,                           DataGridViewRowCancelEventArgs e) {    if (MapRow(e.Row.Index)>=0)    { movieList.RemoveAt(e.Row.Index); }    if (e.Row.Index == currRow)    {       currRow = -1;       currMovie = null;    } } // Maps grid row to row in cache. More logic would be added // for application that refreshes cache from database. private int MapRow(int dgvRow) {    if (dgvRow < movieList.Count)return dgvRow;       else return -1; } 

This example provides only the basic details for implementing a virtual DataGridView. The next step is to extend it to include a virtual memory manager that reloads the cache when data must be fetched from disk to display a cell.

     < Day Day Up > 


    Core C# and  .NET
    Core C# and .NET
    ISBN: 131472275
    EAN: N/A
    Year: 2005
    Pages: 219

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