Chapter 7 Working with the Windows Data Grid Control

 

We're always looking for a way to display data in a manner that will allow the user to quickly browse and change data. Every version of Microsoft Visual Studio has had a data grid control that is meant to provide easy browse and change capability with minimal effort by the developer. This chapter will examine the Windows data grid control, called the DataGridView, which is available in Visual Studio 2005; the next chapter will examine the Web data grid control, called the GridView.

Understanding the DataGridView Control

The DataGridView control is used on Windows Forms applications to display data in a tabular, rows-and-columns format. Earlier grid controls were difficult to customize without diving deep into Windows handles and messages, but the DataGridView object exposes more than 160 events to make it much simpler to access the key area of the grid.

The basic structure of the DataGridView object is shown in Figure 7-1. The DataGridView object consists of collections of rows and columns. The Rows property provides a collection of DataGridViewRow objects. Each DataGridViewRow object contains a Cells collection, which is a collection of objects that derive from the DataGridViewCell class. The Columns property provides a collection of objects that derive from the DataGridViewColumn class. Although the DataGridViewRow object holds the collection of cells, each DataGridViewColumn provides an instance of a specific cell that is used as a template. The DataGridViewRow object can access the cell template to create clones of the correct cell type.

image from book
Figure 7-1: The basic structure of the DataGridView object

What do I mean by correct cell type? Figure 7-2 shows the class hierarchy of the DataGridViewCell. With the exception of the header types, the class hierarchy of the DataGridViewColumn class is similar to that of the DataGridViewCell class. You don't define cell types for your DataGridView object you define columns, and each column object supplies a cell object to the row; the row uses the cells as templates for creating the proper cell types for the row.

image from book
Figure 7-2: The DataGridViewCell class hierarchy

Formatting with Styles

You use styles to format the DataGridView object. You can assign styles to individual cells, rows, or columns or to the whole DataGridView object. The effective style of a cell is derived by evaluating from the least specific style (the DataGridView object) to the most specific style (the cell style). The style properties are additive, which means that when the BackColor property is set to red on the DataGridView object and the Font is set to Arial on the cell, the cell renders with the Arial font and a red background. When there is a conflict, such as different BackColor settings, the most specific setting prevails.

You can use the CellFormatting event to control the style programmatically. The benefit is that you can apply business rules to specify whether a cell should stand out from other cells (for example, making the negative "quantity on hand" number red, but only when the inventory item sells more than one item per month).

DataGridView Modes of Operation

The DataGridView supports three modes of operation: bound, unbound, and virtual. You are in bound mode when you bind, or connect, the DataGridView object to a data source. You are in unbound mode when you simply populate the row data programmatically. It is also possible to be in a "mixed" mode, where you bind to a data source but also add one or more unbound columns to the DataGridView object.

Virtual mode allows you to control the DataGridView object's interaction with your data cache. The primary benefit of virtual mode is that you can implement just-in-time data loading when you have large amounts of data to make available to the user but the user can select the data to be viewed. In other words, rather than filling a DataSet object with a couple of gigabytes of data, you can simply populate the DataSet with the data that is necessary when the user's interaction causes a request for specific data.

Binding to a Data Source

The DataGridView can be easily bound to a data source at design time; Visual Studio will read your data source and populate the columns collection. You can select or create the data source by using the Choose Data Source dialog box.

The data source can be one of the following:

Resource Sharing

The DataGridView object attempts to minimize resource usage by sharing DataGridViewRow instances wherever possible. A row can be shared if the state of all of its cells can be determined based on the state of the rows and columns that contain the cells. The state of a cell refers to a cell's appearance and behavior, which is typically derived from the column and row that the cell belongs to. Changing the state of a cell at the cell level so that its state cannot be determined via the row and column it belongs to causes a shared row to become unshared.

Here are some reasons that one or more rows can be unshared or can become unshared.

DataGridView Setup

The following examples use the Employees table in the Northwind database; the Northwind database base files (Northwnd.mdf and Northwnd.ldf) were simply included in the project to create a typed DataSet.

Before working with a DataGridView object, you must configure a DataSource. For example, you can configure a DataSource by including a database in your project. When the Northwind database is added to the project, the Data Source Configuration Wizard starts. The first page prompts for the list of tables, views, functions, and stored procedures that should be included in the DataSource. Figure 7-3 shows all of the tables being selected except the sysdiagrams table because the sysdiagrams table holds metadata that is used by the database diagram tool. Also notice that the name of the DataSet to be created has been changed from northwindDataSet to NorthwindDs.

image from book
Figure 7-3: To set up the Northwind DataSource, you simply add the Northwind database to the project, which will start the Data Source Configuration Wizard.

After you create the DataSource, you can simply drag and drop the Employees table onto the form, which will add a DataGridView object to the form. The other objects that have been added to the form are as follows. These objects are shown in Figure 7-4.

image from book
Figure 7-4: These objects are added to the tray when the Employees table is dragged onto the form.

When the DataGridView is dropped onto a Windows form, the DataGridView Tasks window is displayed (Figure 7-4). This window gives you quick access to some of the common tasks for setting up the DataGridView quickly. Notice the little arrow button attached to the upper left corner of the DataGridView Tasks window, which you can use to hide or display the window.

Working with Cell Events

A cell can generate many events, which are bubbled up to the DataGridView object. These events substantially increase the flexibility of the DataGridView over data grid controls of the past. Table 7-1 lists the available cell events.

Table 7-1: Cell Events Available on the DataGridView Object

Event

Description

CellBeginEdit

A cell has entered edit mode.

CellBorderStyleChanged

The border style was changed for a cell.

CellClick

Any part of the cell has been clicked using the left mouse button.

CellContentClicked

The content of the cell has been clicked.

CellContentDoubleClick

The content of the cell has been double-clicked.

CellContextMenuStripChanged

The shortcut menu associated with a cell has been changed.

CellContextMenuStripNeeded

A shortcut menu is needed for a cell.

CellDoubleClick

Any part of a cell has been double-clicked using the left mouse button.

CellEndEdit

The currently selected cell has exited edit mode.

CellEnter

A cell has received focus and is becoming the currently selected cell.

CellErrorTextChanged

The error text of a cell has changed.

CellErrorTextNeeded

The error text of a cell is needed. At this time, the error text can be read or changed before it is displayed.

CellFormatting

A cell that is to be displayed needs to be formatted.

CellLeave

A cell has lost focus and is no longer the currently selected cell.

CellMouseClick

A mouse click has taken place anywhere on a cell using any mouse button. Use the CellClick event if you are only interested in left mouse button clicks.

CellMouseDoubleClick

A mouse double-click has taken place anywhere on a cell using any mouse button. Use the CellDoubleClick event if you are only interested in left mouse button double-clicks.

CellMouseDown

A mouse button was pressed while the mouse pointer was within a cell.

CellMouseEnter

The mouse pointer just entered a cell.

CellMouseLeave

The mouse pointer just left a cell.

CellMouseMove

The mouse pointer was moved over a cell.

CellMouseUp

A mouse button was released while the mouse pointer was within a cell.

CellPainting

A cell needs to be drawn.

CellParsing

A cell (either modified or not) is leaving edit mode.

CellStateChanged

A cell state has changed, typically to Selected or None.

CellStyleChanged

A style has been assigned to a cell. Note that this event does not fire if a property of the style, such as BackColor, changes.

CellStyleContentChanged

One of the properties of the style, such as BackColor, has changed.

CellToolTipTextChanged

A cell's ToolTipText has changed.

CellToolTipTextNeeded

A cell's ToolTipText is needed. At this time, the ToolTipText can be viewed or changed before it is displayed.

CellValidated

A cell has finished validation.

CellValidating

A cell is being validated, and the validation can be canceled.

CellValueChanged

The value of the cell has been changed.

CellValueNeeded

A value is needed for the cell to be formatted and displayed. This event fires only if DataGridView.VirtualMode is true.

CellValuePushed

A cell value has changed and needs to be stored in the underlying data source. This event fires only if DataGridView.VirtualMode is true.

CurrentCellChanged

A different cell is becoming the current cell.

CurrentCellDirtyStateChanged

The current cell's value has changed (for example, if a cell is in edit mode and a single character is typed). This event fires again when the dirty state has been cleared.

DefaultCellStyleChanged

A style has been assigned to the DataGridView object's DefaultCellStyle property.

You can view a list of the DataGridView object's events by clicking the DataGridView object and then clicking the event lightning bolt icon in the Properties window (Figure 7-5). You can add event handler stub code for any of the events by simply double-clicking the event.

image from book
Figure 7-5: DataGridView object events

Event Sequence

Here are the events that take place when a cell is entered (the Current Cell) and edited, and then another cell is clicked (the Next Cell).

  1. CellStateChanged (to Selected): Current Cell

  2. CellEnter: Current Cell

  3. CellFormatting: Current Cell

  4. CellClick: Current Cell

  5. CellMouseClick: Current Cell

  6. CellFormatting: Current Cell

  7. CellBeginEdit: Current Cell

  8. CellFormatting: Current Cell

  9. EditingControlShowing: Current Cell

  10. CellDirtyStateChanged (to dirty): A character was typed into Current Cell

  11. CellMouseLeave: Current Cell

  12. CellMouseEnter: Next Cell

  13. CellFormatting: Next Cell

  14. CellMouseDown: Next Cell

  15. CellLeave: Current Cell

  16. CellValidating: Current Cell

  17. CellParsing: Current Cell

  18. CellValueChanged: Current Cell

  19. CellDirtyStateChanged (to clear): Current Cell

  20. CellValidated: Current Cell

  21. CellFormatting: Current Cell

  22. CellStateChanged (to None): Current Cell

  23. CellStateChanged (to Selected): Next Cell

  24. CellEndEdit: Current Cell

Now that you have a table of the cell events (Table 7-1) and the preceding sequential list of events, you can code the necessary event handlers to customize the DataGridView object. For example, if you want to keep a user from changing from one cell to another when the user does not enter an appropriate value into the first cell, you can set the Cancel property to true in the CellValidating event handler, as shown in the following code snippet.

image from book

Visual Basic

Private Sub EmployeesDataGridView_CellValidating(_       ByVal sender As System.Object, _       ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) _       Handles EmployeesDataGridView.CellValidating    Debug.WriteLine("Cell Validating: " _    + e.RowIndex.ToString() + ", " + e.ColumnIndex.ToString())    'Check Last Name to see if last name has at least 1 character    If (e.ColumnIndex = 1 _    And e.FormattedValue.ToString().Trim().Length = 0) Then       e.Cancel = True    End If End Sub 
image from book

image from book

C#

private void employeesDataGridView_CellValidating(    object sender, DataGridViewCellValidatingEventArgs e) {    Debug.WriteLine("Cell Validating: "       + e.RowIndex.ToString() + ","+e.ColumnIndex.ToString());    //Check to see if last name has at least 1 character    if (e.ColumnIndex==1 //Last Name       && e.FormattedValue.ToString().Trim().Length==0)    {       e.Cancel = true;    } } 
image from book

When the code is run and the user attempts to change the last name to an empty string or a string that consists of spaces, the Cancel property will be set to true and the user will not be allowed to leave the cell. Notice that the events will take place in the following order.

  1. CellLeave: Current Cell

  2. CellFormatting: Current Cell

  3. CellValidating: Current Cell

  4. CellEnter: Current Cell

You can see that the CellLeave event fired, and after CellValidating set the Cancel property to true, the CellEnter event fired as the user was placed back into the cell.

Working with DataGridViewColumn Objects

The DataGridView object has a Columns collection, which contains objects that inherit from the DataGridViewColumn class. The Microsoft .NET Framework contains several column types, but you can also create your own column types. Figure 7-6 shows the DataGridViewColumn class hierarchy, with the available column types.

image from book
Figure 7-6: The DataGridViewColumn class hierarchy

Editing the Column List

The DataGridView designer lets you add and remove columns by using the Edit Columns dialog box. You can also populate the columns collection at runtime using your own code.

Working with Column Events

Many column-related events are available on the DataGridView object, as shown in Table 7-2. Notice that some of the events are based on individual column objects, while other events are based on a global column-related property being changed on the DataGridView object.

Table 7-2: Column Events on the DataGridView Object

Event

Description

AllowUserToOrderColumnsChanged

The AllowUserToOrderColumns property of the DataGridView object has been changed.

AllowUserToResizeColumnsChanged

The AllowUserToResizeColumns property of the DataGridView object has been changed.

AutoSizeColumnModeChanged

The AutoSizeMode property on a column object has been changed.

AutoSizeColumnsModeChanged

The AutoSizeColumnsMode property of the DataGridView object has been changed.

ColumnAdded

A column has been added to the DataGridView object.

ColumnContextMenuStripChanged

A column object's ContextMenuStrip property has been changed.

ColumnDataPropertyNameChanged

The DataPropertyName property of a column object has been changed.

ColumnDefaultCellStyleChanged

The DefaultCellStyle property of a column object has been changed.

ColumnDisplayIndexChanged

The DisplayIndex property of a column object has been changed.

ColumnDividerDoubleClick

A user double-clicked the divider between two columns.

ColumnDividerWidthChanged

The width of a column divider changed by setting the DividerWidth property on the column.

ColumnHeaderCellChanged

A column object's header cell contents have been changed.

ColumnHeaderMouseClick

A user clicked a column object's header.

ColumnHeaderMouseDoubleClick

A user double-clicked a column object's header.

ColumnHeadersBorderStyleChanged

The ColumnHeadersBorderStyle property of the DataGridView object has been changed.

ColumnHeadersDefaultCellStyleChanged

The ColumnHeadersDefaultCellStyle property of the DataGridView object has been changed.

ColumnHeaderHeightChanged

The ColumnHeaderHeight property of the DataGridView object has been changed.

ColumnHeaderHeightSizeModeChanged

The ColumnHeaderHeightSizeMode property of the DataGridView object has been changed.

ColumnMinimumWidthChanged

The ColumnMinimumWidth property on a column object has been changed.

ColumnNameChanged

The Name property on a column has been changed.

ColumnRemoved

A column object has been removed from the DataGridView object.

ColumnSortModeChanged

The SortMode property of a column object has been changed.

ColumnStateChanged

A column object's state has changed (for example, from Displayed or None to indicate whether the column is currently being displayed).

ColumnToolTipTextChanged

The ToolTipText property of a column object has been changed.

ColumnWidthChange

The Width property of a column has been changed.

Using the DataGridViewTextBoxColumn

The default column type for string and numeric data is the DataGridViewTextBoxColumn, which supplies a DataGridViewTextBoxCell, as a cell template, to a row that is being created. The row queries the column for the cell template and clones the cell, which ensures that the template is not modified by the row.

The DataGridViewTextBoxCell simply renders data as text and accepts text input. For a given DataGridViewTextBoxColumn object, there is one DataGridViewTextBoxCell object for each DataGridViewRow object in the DataGridView object. When a cell becomes activated, a DataGridViewTextBoxEditingControl object is supplied so that the application user can edit the cell, provided that the cell's ReadOnly property is set to false.

Using the DataGridViewCheckBoxColumn

The default column type for Boolean data (Bit data type in SQL Server) is the DataGridViewCheckBoxColumn, which supplies a DataGridViewCheckBoxCell, as a cell template, to a row that is being created.

The DataGridViewCheckBoxCell renders data in a CheckBox and lets the user edit the value by selecting or deselecting the CheckBox. For a given DataGridViewCheckBoxColumn object, there is one DataGridViewCheckBoxCell object for each DataGridViewRow object in the DataGridView object. The DataGridViewCheckBoxColumn has a property called ThreeState that can be set to true to allow the CheckBox to have a selected (true), deselected (false), or indeterminate (null) state.

Probably the most common event you will be interested in is the CellContentClick event, which fires when the user clicks in the CheckBox to change the selected state.

Using the DataGridViewImageColumn

The DataGridViewImageColumn can be used to display images and icons in the DataGridView object. By default, the Visual Studio Data Source designer does not assign a data source image field to a DataGridView column, so images are not displayed, but a DataGridViewImageColumn can be added to the DataGridView object for displaying image and icon data. The DataGridViewImageColumn supplies a DataGridViewImageCell as a cell template to a row that is being created.

The DataGridViewImageCell renders data directly into the cell. Figure 7-7 shows the photos from the Employees table rendered using a DataGridViewImageColumn object. In this example, the AutoResizeRowsMode of the EmployeeDataGridView object and the AutoSizeMode of the DatagridViewImageColumn have been set to DisplayedCells.

image from book
Figure 7-7: The Employees table contains a Photo column that can be rendered using the DataGridViewImageCell.

Loading a New Image into the DataGridViewImageCell from a File The DataGridViewImageColumn easily displays an image, but if you want to replace the image with a new image, or if you are adding a new employee, you'll need to add some code to get the ability to replace the image. Using the current example project as shown in Figure 7-7, add a Context-MenuStrip to the form and set its Name property to PhotoMenu. Next, add a menu item and set the Text property to "&Insert Photo From File". Assign the PhotoMenu to the ContextMenuStrip property of the Photo column.

Add an event handler for the click event of the "&Insert Photo From File" menu item. Add the following code, which prompts the user for an image file and loads it.

image from book

Visual Basic

Dim currentPhotoCell as DataGridViewImageCell Private Sub InsertPhotoFromFileToolStripMenuItem_Click( _       ByVal sender As System.Object, ByVal e As System.EventArgs) _       Handles InsertPhotoFromFileToolStripMenuItem.Click    dim dlg as new OpenFileDialog()    dlg.Multiselect = false    dlg.Filter = "GIF|*.gif|BMP|*.bmp|JPEG|*.jpg;*.jpeg|All Files|*.*"    dlg.FilterIndex = 3    dlg.Title = "Select a photo to insert"    if Windows.Forms.DialogResult.OK <> dlg.ShowDialog() then return    Dim bmp as new Bitmap(dlg.FileName)    currentPhotoCell.Value = bmp End Sub 
image from book

image from book

C#

DataGridViewImageCell currentPhotoCell; private void insertPhotoToolStripMenuItem_Click(object sender, EventArgs e) {    OpenFileDialog dlg = new OpenFileDialog();    dlg.Multiselect = false;    dlg.Filter = "GIF|*.gif|BMP|*.bmp|JPEG|*.jpg;*.jpeg|All Files|*.*";    dlg.FilterIndex = 3;    dlg.Title = "Select a photo to insert";    if (DialogResult.OK != dlg.ShowDialog()) return;    Bitmap bmp = new Bitmap(dlg.FileName);    currentPhotoCell.Value = bmp; } 
image from book

Most of this code simply sets up the dialog box to prompt for an image file. The file is loaded into a new Bitmap object, and the Bitmap object is assigned to the currentPhotoCell object's Value property.

The only problem with this code is that the currentPhotoCell variable has not been set. You can make currentPhotoCell point to the cell that was right-clicked, by adding an event handler for the CellContextMenuStripNeeded event of the employeesDataGridView object and adding the following code.

image from book

Visual Basic

Private Sub EmployeesDataGridView_CellContextMenuStripNeeded( _       ByVal sender As System.Object, _       ByValeAs_  System.Windows.Forms.DataGridViewCellContextMenuStripNeededEventArgs) _       Handles EmployeesDataGridView.CellContextMenuStripNeeded    Dim dg as DataGridView = CTYpe(sender,DataGridView)    if typeof dg.Columns(e.ColumnIndex) is DataGridViewImageColumn then       currentPhotoCell = _          ctype(dg.Rows(e.RowIndex).Cells(e.ColumnIndex),DataGridViewImageCell)    end if End Sub 
image from book

image from book

C#

private void employeesDataGridView_CellContextMenuStripNeeded(    object sender, DataGridViewCellContextMenuStripNeededEventArgs e) {    DataGridView dg = (DataGridView)sender;    if (dg.Columns[e.ColumnIndex] is DataGridViewImageColumn)    {       currentPhotoCell =          (DataGridViewImageCell)dg.Rows[e.RowIndex].Cells[e.ColumnIndex];    } } 
image from book

Run the project and right-click a photo, which will display the shortcut menu. Click the "Insert Item From File" menu item. Select an image file on your computer and click OK. The image will load into the cell as shown in Figure 7-8. Be sure to click the Save Data icon to store the new image into the database.

image from book
Figure 7-8: You can upload images to the DataGridView with a bit of code.

Saving an Image from the DataGridViewImageCell to a File To save an image that is in a DataGridViewImageCell object to a file, you can add a bit more code to your project. For starters, add another menu item to the PhotoMenu object and set its Text property to "Save Photo to File". Add a click event handler and add the following code.

image from book

Visual Basic

Private Sub SavePhotoToFileToolStripMenuItem_Click( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs) _       Handles SavePhotoToFileToolStripMenuItem.Click    Const oleOffset As Integer = 78    Const oleTypeStart As Integer = 20    Const oleTypeLength As Integer = 12    Dim imageBytes() As Byte = CType(currentPhotoCell.Value, Byte())    If (imageBytes Is Nothing Or imageBytes.Length = 0) Then Return    Dim dlg As New SaveFileDialog()    dlg.AddExtension = True    dlg.Filter = "GIF|*.gif|BMP|*.bmp|JPG|*.jpg"    dlg.FilterIndex = 2    dlg.Title = "Enter a file name for this photo"    dlg.FileName = "EmployeePhoto.bmp"    If (System.Windows.Forms.DialogResult.OK <> dlg.ShowDialog()) Then       Return    End If    Dim tempStream As MemoryStream    Dim type As String = System.Text.Encoding.ASCII.GetString(_       imageBytes, oleTypeStart, oleTypeLength)    If (type = "Bitmap Image") Then       tempStream = New MemoryStream(_          imageBytes, oleOffset, imageBytes.Length - oleOffset)    Else       tempStream = New MemoryStream( _          imageBytes, 0, imageBytes.Length)    End If    Dim bmp As New Bitmap(tempStream)    bmp.Save(dlg.FileName, ParseImageFormat(dlg.FileName)) End Sub Public Function ParseImageFormat(ByVal fileName As String) As ImageFormat    Dim ext As String = Path.GetExtension(fileName).ToLower()    Select Case ext       Case "bmp"          Return ImageFormat.Bmp       Case "jpg"       Case "jpeg"          Return ImageFormat.Jpeg       Case "gif"          Return ImageFormat.Gif       Case Else          Return ImageFormat.Bmp    End Select    Return Nothing End Function 
image from book

image from book

C#

private void savePhotoToFileToolStripMenuItem_Click(       object sender, EventArgs e) {    const int oleOffset = 78;    const int oleTypeStart = 20;    const int oleTypeLength = 12;    byte[] imageBytes = (byte[])currentPhotoCell.Value;    if (imageBytes == null || imageBytes.Length == 0) return;    SaveFileDialog dlg = new SaveFileDialog();    dlg.AddExtension = true;    dlg.Filter = "GIF|*.gif|BMP|*.bmp|JPG|*.jpg";    dlg.FilterIndex = 2;    dlg.Title = "Enter a file name for this photo";    dlg.FileName = "EmployeePhoto.bmp";    if (DialogResult.OK != dlg.ShowDialog()) return;    MemoryStream tempStream;    string type = System.Text.Encoding.ASCII.GetString(       imageBytes, oleTypeStart, oleTypeLength);    if (type == "Bitmap Image")    {       tempStream = new MemoryStream(          imageBytes, oleOffset, imageBytes.Length - oleOffset);    }    else    {       tempStream = new MemoryStream(          imageBytes, 0, imageBytes.Length);    }    Bitmap bmp = new Bitmap(tempStream);    bmp.Save(dlg.FileName, ParseImageFormat(dlg.FileName)); } public ImageFormat ParseImageFormat(string fileName) {    string ext = Path.GetExtension(fileName).ToLower();    switch (ext)    {       case "bmp":          return ImageFormat.Bmp;       case "jpg":       case "jpeg":          return ImageFormat.Jpeg;       case "gif":          return ImageFormat.Gif;       default:          return ImageFormat.Bmp;    } } 
image from book

There is a small problem with the employee photos that are embedded in the Northwind database. These photos were originally in a Microsoft Access database, which stored the photos with an OLE header. The OLE header occupies the first 78 bytes of the image byte array. If you want to save these as reuseable images, you need to strip off the OLE header. That's OK, but in the previous section, I showed you how to insert new images that were saved to the database into the DataGridView object. This means that some images may have the OLE header, while others don't, so you need a means for determining whether the OLE header exists. As it turns out, bytes 20 through 31 (12 total bytes) will hold the string "Bitmap Image" if the OLE header exists.

With that information, let's see what this code is doing. First, constants are declared that relate to the OLE header information. Next, a byte array variable called imageBytes is created to simplify access to the value that is in the currentPhotoCell. The imageBytes variable is tested to see if it contains an image. If it does not have an image, there is no need to go further.

The code then prompts the user to enter a filename. Notice that you have the option to save the image as a GIF, BMP, or JPG file, even if the image was originally stored in a different format. By the way, the original employee photos are in BMP format.

This code then looks for the "Bitmap Image" string, and if it exists, the OLE header will be stripped by creating a MemoryStream object from the imageBytes, starting at the OLE header offset.

Finally, a Bitmap object is created from the MemoryStream object. If the MemoryStream object contains a valid image, the Bitmap object will be successfully created in memory. If the MemoryStream object does not contain a valid image, or you didn't strip off the OLE header, an ArgumentException would be thrown with the message "Parameter is not valid". The Bitmap object is then saved to the file, using the format that you selected. Notice that a helper function was created to get the image format based on the file extension.

Using the DataGridViewButtonColumn

The DataGridViewButtonColumn supplies a DataGridViewButtonCell as a cell template to a row that is being created. The DataGridViewButtonCell renders the whole cell as a button. For a given DataGridViewButtonColumn object, there is one DataGridViewButtonCell object for each DataGridViewRow object in the DataGridView object. The CellClick and CellContentClick events fire when the button is clicked.

Using the DataGridViewLinkColumn

The DataGridViewLinkColumn functions like the DataGridViewButtonColumn but looks like a hyperlink instead of a button. It supplies a DataGridViewLinkCell as a cell template to a row that is being created. For a given DataGridViewLinkColumn object, there is one DataGridViewLinkCell object for each DataGridViewRow object in the DataGridView object. The CellClick and CellContentClick events fire when the button is clicked.

Using the DataGridViewComboBoxColumn

The DataGridViewComboBoxColumn supplies a DataGridViewComboBoxCell as a cell template to a row that is being created. For a given DataGridViewComboBoxColumn object, there is one DataGridViewComboBoxCell object for each DataGridViewRow object in the DataGridView object. The DataGridViewComboBoxCell renders the whole cell as a ComboBox. You can set the DisplayStyleForCurrentCellOnly property to true, which causes these cells to render as TextBox objects until you select one, and then the cell renders with a ComboBox (as shown in Figure 7-9). The DataGridViewComboBoxColumn can receive its pick list from another data source if you make assignments to the DataSource, DisplayMember, and ValueMember properties.

image from book
Figure 7-9: The DisplayStyleForCurrentCellOnly is set to false in the CustomerID column and is set to true in the EmployeeID column.

The CellClick fires every time the user activates the drop-down list; the CellContentClick event fires only when the user first clicks in the cell. The CellValueChanged event fires if the user changes the contents of the cell and leaves the cell.

Working with DataGridViewRow Objects

A DataGridViewRow object is created for each row of data that needs to be rendered in the DataGridView object. Remember that the DataGridViewRow object contains a collection of cell objects that inherit from the DataGridViewCell class, where each of the columns supplies a cell template that the row clones when the row is created.

Table 7-3 shows the events that are related to the DataGridViewRow.

Table 7-3: Row Events on the DataGridView Object

Event

Description

AllowUserToAddRowsChanged

The AllowUserToAddRows property of the DataGridView object has been changed.

AllowUserToDeleteRowsChanged

The AllowUserToDeleteRows property of the DataGridView object has been changed.

AllowUserToResizeRowsChanged

The AllowUserToResizeRows property of the DataGridView object has been changed.

AlternatingRowsDefaultCellStyleChanged

The AlternatingRowsDefaultCellStyle property of the DataGridView object has been changed.

AutoSizeRowsModeChanged

The AutoSizeRowsMode property of the DataGridView object has been changed.

CancelRowEdit

The row edit is being canceled (fires only if the DataGridView object is in virtual mode).

NewRowNeeded

The user has navigated to the new row on the bottom of the DataGridView object (fires only if the DataGridView object is in virtual mode).

RowContextMenuStripChanged

The ContextMenuStrip property of the DataGridView object has changed.

RowContextMenuStripNeeded

A shortcut menu strip needs to be displayed for the row.

RowDefaultCellStyleChanged

The DefaultGridViewBand object's DefaultCellStyle property has been changed. This event is typically triggered when the RowTemplate of the DataGridView object is changed.

RowDirtyStateNeeded

The DataGridView object is attempting to determine whether the row has uncommitted changes (fires only if the DataGridView is in virtual mode).

RowDividerDoubleClick

The row divider has been double-clicked.

RowDividerHeightChanged

The row divider's height has been changed.

RowEnter

A row gets the focus and becomes the current row.

RowErrorTextChanged

The error text of a row has changed.

RowErrorTextNeeded

The row error text is about to be displayed (fires only if the DataGridView object is in virtual mode).

RowHeaderCellChanged

A row object's header cell contents have been changed.

RowHeaderMouseClick

The user has clicked the row header.

RowHeaderMouseDoubleClick

The user has double-clicked the row header.

RowHeaderBorderStyleChanged

The RowHeaderBorderStyle property of the DataGridView object has been changed.

RowHeaderDefaultCellStyleChanged

The RowHeaderDefaultCellStyle property of the DataGridView object has been set.

RowHeadersWidthChanged

The RowHeadersWidth property of the DataGridView object has been changed.

RowHeadersWidthSizeModeChanged

The RowHeadersWidthSizeMode property of the DataGridView object has been changed.

RowHeightChanged

The Height property of a DataGridViewRow object has been changed.

RowHeightInfoNeeded

The Height property of a DataGridViewRow has been requested.

RowHeightInfoPushed

The user has changed the height of a row.

RowLeave

The user has left the row, causing the row to lose focus and no longer be the current row.

RowMinimumHeightChanged

The MinimumHeight property on a DataGridViewRow object has been changed.

RowPostPaint

The row object's cells have been painted.

RowPrePaint

This event fires before any of the row object's cells are painted.

RowsAdded

One or more rows have been added to the DataGridView object's Rows collection.

RowsDefaultCellStyleChanged

The RowsDefaultCellStyle property of the DataGridView object has been set.

RowsRemoved

One or more rows have been removed from the DataGridView object's Rows collection.

RowStateChanged

The state of the row has changed, typically to indicate that the row is being displayed.

RowUnshared

The row object's state has been changed from shared to unshared.

RowValidated

The row has been validated.

RowValidating

The row is being validated. This event also allows validation to fail by setting the Cancel property of e (the DataGridViewCellCancelEventArgs) to true.

UserAddedRow

The user has added a row.

UserDeletedRow

The user has deleted a row.

UserDeletingRow

The user is attempting to delete a row. The delete can be aborted by setting the Cancel property of e (the DataGridViewRowCancelEventArgs) to true.

Implementing Virtual Mode

You can implement virtual mode by setting the VirtualMode property of the DataGridView object to true and implementing event handlers to populate and edit cells as necessary. In a read-only scenario, the only event that needs to be handled is CellValueNeeded. Table 7-4 shows the other events that are available for use only when virtual mode is enabled.

Table 7-4: Virtual Mode Events on the DataGridView Object

Event

Description

CellValueNeeded

A request has been made for cell data to be displayed from the data cache.

CellValuePushed

Used to commit modified cell data back to the data cache.

NewRowNeeded

A request has been made for row data to be displayed from the data cache.

RowDirtyStateNeeded

A request has been made to retrieve the dirty state of a row from the data cache. A dirty row is a row with uncommitted changes.

CancelRowEdit

A request has been made to roll back changes in the cell to the original data cache values.

RowErrorTextNeeded

A request has been made for row error text. If the error text has changed, be sure to call the UpdateRowErrorText method to ensure that the proper error text is displayed in the DataGridView object.

To keep this example simple, we will work with a Fruit class and a Fruit collection. The Fruit class is shown in the following code snippet.

image from book

Visual Basic

Public Class Fruit    Public Property Name() As String       Get          Return _name       End Get       Set(ByVal value As String)          _name = value       End Set    End Property    Private _name As String    Public Property Calories() As Decimal       Get         Return _calories       End Get       Set(ByVal value As Decimal)          _calories = value       End Set    End Property    Private _calories As Decimal    Public Property Carbs() As Decimal       Get         Return _carbs       End Get       Set(ByVal value As Decimal)          _carbs = value       End Set    End Property    Private _carbs As Decimal    Public Sub New(ByVal name As String,          ByVal calories As Decimal, _          ByVal carbs As Decimal)       Me.Name = name       Me.Calories = calories       Me.Carbs = carbs    End Sub End Class 
image from book

image from book

C#

namespace WinFormDataGridView {    public class Fruit    {       public string Name       {          get { return _name; }          set { _name = value; }       }       private string _name;       public decimal Calories       {          get { return _calories; }          set { _calories = value; }       }       private decimal _calories;       public decimal Carbs       {          get { return _carbs; }          set { _carbs = value; }       }       private decimal _carbs;       public Fruit(string name, decimal calories, decimal carbs)       {          this.Name = name;          this.Calories = calories;          this.Carbs = carbs;       }    } } 
image from book

The following steps are required to implement virtual mode.

  1. Add a DataGridView object to a Windows form and set its VirtualMode property to true. Add three DataGridViewTextBoxColumn objects for FruitName, Calories, and Carbs. Rather than code this, right-click the DataGridView control in Visual Studio's form designer and choose Add Column to configure these settings.

  2. Be sure to add the Fruit class as shown in the previous code example. Add an instance variable to the form that is a fruit list object. In the form constructor, add code to populate the fruit list. Set the RowCount of the DataGridView object to the count of items in the fruit list, plus one to provide the ability to add a new row. Lastly, add instance variables to hold a reference to the fruit being edited and its index number.

  3. Add an event handler for the DataGridView object's CellValueNeeded event. This event fires when a cell needs to be painted, and this code populates the grid cell in a just-in-time fashion. What this means is that the DataGridView object will request this information when a cell needs to be displayed. This essentially lets the DataGridView object act as a sliding window into your data. The code for these steps is as follows:

image from book

Visual Basic

Public Class Form4    Private fruitList As New List(Of Fruit)    Private rowInEdit As Integer = -1    Private fruitInEdit As Fruit = Nothing    Private Sub Form4_Load(ByVal sender As System.Object, _          ByVal e As System.EventArgs) Handles MyBase.Load       'populate sample data       fruitList.Add(New Fruit("Apple", 44, 10.5))       fruitList.Add(New Fruit("Banana", 107, 26))       fruitList.Add(New Fruit("Orange", 35, 8.5))       'Add 1 for new row       DataGridView1.RowCount = fruitList.Count + 1    End Sub    'occurs when a cell needs to be painted    Private Sub DataGridView1_CellValueNeeded(          ByVal sender As System.Object, _          ByVal e As _          System.Windows.Forms.DataGridViewCellValueEventArgs) _          Handles DataGridView1.CellValueNeeded       'do not return anything on the "*" row       If e.RowIndex = DataGridView1.RowCount   1 Then          'not needed for new row          Return       End If       Dim tmpFruit As Fruit = Nothing       ' if the row is being edited       ' get the fruitInEdit else get       ' the appropriate fruitList       ' reference.       If e.RowIndex = rowInEdit Then          tmpFruit = Me.fruitInEdit       Else          tmpFruit = fruitList(e.RowIndex)       End If       ' Set the cell value based on mapping the       ' column name to the property.       Select Case Me.DataGridView1.Columns(e.ColumnIndex).Name          Case "FruitName"             e.Value = tmpFruit.Name          Case "Calories"             e.Value = tmpFruit.Calories          Case "Carbs"             e.Value = tmpFruit.Carbs       End Select    End Sub End Class 
image from book

image from book

C#

using System; using System.Collections.Generic; using System.Windows.Forms; namespace WinFormDataGridView {    public partial class Form4 : Form    {       private List<Fruit> fruitList = new List<Fruit>();       private int rowInEdit = -1;       private Fruit fruitInEdit = null;       public Form4()       {          InitializeComponent();       }       private void Form4_Load(object sender, EventArgs e)       {          //populate sample data          fruitList.Add(new Fruit("Apple", 44M, 10.5M));          fruitList.Add(new Fruit("Banana", 107M, 26M));          fruitList.Add(new Fruit("Orange", 35M, 8.5M));          // Add 1 for new row          DataGridView1.RowCount = fruitList.Count + 1;       }       //occurs when a cell needs to be painted       private void DataGridView1_CellValueNeeded(object sender,             DataGridViewCellValueEventArgs e)       {          //don't return anything on the "*" row          if (e.RowIndex == DataGridView1.RowCount - 1)          {             // not needed for new row             return;          }          Fruit tmpFruit = null;          // if the row is being edited          // get the fruitInEdit else get          // the appropriate fruitList          // reference.         if (e.RowIndex == rowInEdit)          {             tmpFruit = fruitInEdit;          }          else          {             tmpFruit = fruitList[e.RowIndex];          }          // Set the cell value based on mapping the          // column name to the property.          switch (DataGridView1.Columns[e.ColumnIndex].Name)          {             case "FruitName":                e.Value = tmpFruit.Name; break;             case "Calories":                e.Value = tmpFruit.Calories; break;             case "Carbs":                e.Value = tmpFruit.Carbs; break;          }       }    } } 
image from book

With this code entered, the DataGridView object can be displayed with its contents, as shown in Figure 7-10.When the CellValueNeeded event handler is called, this code will return nothing if the cell to be painted is in the "*" (new) row. If the cell to be painted is in a row that is being edited, the column value from the fruitInEdit variable will be returned. If this is a column in a row that is not being edited, the fruit will be retrieved from the fruitList collection and its column value will be returned.

image from book
Figure 7-10: The DataGridView object was populated using the CellValueNeeded event.

The next step is to add code to handle the addition of a new row to the DataGridView object. The NewRowNeeded event must be implemented to create a new Fruit instance when a new row is created in the DataGridView object.

Code must also be added to commit cell changes back to the fruit list. This requires implementation of the CellValuePushed event and the RowValidated event. These events are implemented as shown in the following code snippets:

image from book

Visual Basic

Private Sub DataGridView1_NewRowNeeded( _       ByVal sender As System.Object, _       ByVal e As System.Windows.Forms.DataGridViewRowEventArgs) _       Handles DataGridView1.NewRowNeeded    ' Create a new Fruit object when the user    ' moves the cursor into the "*" row    fruitInEdit = New Fruit("", 0, 0)    rowInEdit = DataGridView1.Rows.Count - 1 End Sub Private Sub DataGridView1_CellValuePushed( _       ByVal sender As System.Object, _       ByVal e As _       System.Windows.Forms.DataGridViewCellValueEventArgs) _       Handles DataGridView1.CellValuePushed    Dim tmpFruit As Fruit = Nothing    ' Store a reference to the Fruit object for the row.    If e.RowIndex < fruitList.Count Then       ' If the user has started editing an       ' existing row, create a clone to edit.       If fruitInEdit Is Nothing Then          fruitInEdit = New Fruit(_             fruitList(e.RowIndex).Name, _             fruitList(e.RowIndex).Calories, _             fruitList(e.RowIndex).Carbs)       End If       tmpFruit = fruitInEdit       rowInEdit = e.RowIndex    Else       ' get the row that's being edited       tmpFruit = fruitInEdit    End If    ' Set the appropriate Fruit property to the cell    ' value entered.    Dim newValue As String = TryCast(e.Value, String)    ' Set the appropriate Fruit property to the cell value entered.    Select Case DataGridView1.Columns(e.ColumnIndex).Name       Case "FruitName"          tmpFruit.Name = newValue       Case "Calories"          tmpFruit.Calories = decimal.Parse(newValue)       Case "Carbs"          tmpFruit.Carbs = decimal.Parse(newValue)    End Select End Sub Private Sub DataGridView1_RowValidated( _       ByVal sender As System.Object, _       ByValeAs _       System.Windows.Forms.DataGridViewCellEventArgs) _       Handles DataGridView1.RowValidated    ' Save row changes if any were made and    ' release edited Fruit object.    If e.RowIndex >= fruitList.Count And _          e.RowIndex <> DataGridView1.Rows.Count - 1 Then       ' Add the new fruit object to the data store.       fruitList.Add(fruitInEdit)       fruitInEdit = Nothing       rowInEdit = -1    ElseIf Not (fruitInEdit Is Nothing) And _          e.RowIndex < fruitList.Count Then       ' Overwrite existing Fruit object       ' with modified fruit.       fruitList(e.RowIndex) = fruitInEdit       fruitInEdit = Nothing       rowInEdit = -1    Else       'clear the edit       fruitInEdit = Nothing       rowInEdit = -1    End If End Sub 
image from book

image from book

C#

private void DataGridView1_NewRowNeeded(object sender,       DataGridViewRowEventArgs e) {    // Create a new Fruit object when the user    // moves the cursor into the "*" row    fruitInEdit = new Fruit("", 0, 0);    rowInEdit = DataGridView1.Rows.Count - 1; } private void DataGridView1_CellValuePushed(object sender,       DataGridViewCellValueEventArgs e) {    Fruit tmpFruit = null;    // Store a reference to the Fruit object for the row.    if (e.RowIndex < fruitList.Count)    {       // If the user is editing a new row, create       // a new Fruit object.       if (fruitInEdit == null)       {          fruitInEdit = new Fruit(             fruitList[e.RowIndex].Name,             fruitList[e.RowIndex].Calories,             fruitList[e.RowIndex].Carbs);       }       tmpFruit = fruitInEdit;       rowInEdit = e.RowIndex;    }    else    {       tmpFruit = fruitInEdit;    }    // Set the appropriate Fruit property to the    // cell value entered.    string newValue = e.Value as string;    switch (DataGridView1.Columns[e.ColumnIndex].Name)    {       case "FruitName":          tmpFruit.Name = newValue; break;       case "Calories":          tmpFruit.Calories = decimal.Parse(newValue); break;       case "Carbs":          tmpFruit.Carbs = decimal.Parse(newValue); break;    } } private void DataGridView1_RowValidated(object sender,       DataGridViewCellEventArgs e) {    // Save row changes if any were made and    // release edited Fruit object.    if (e.RowIndex >= fruitList.Count &&    e.RowIndex != DataGridView1.Rows.Count - 1)    {       // Add the new fruit object to the data store.       fruitList.Add(fruitInEdit);       fruitInEdit = null;       rowInEdit = -1;    }    else if ((fruitInEdit != null) &&       (e.RowIndex < fruitList.Count))    {       // Overwrite existing Fruit object       // with modified fruit.       fruitList[e.RowIndex] = fruitInEdit;       fruitInEdit = null;       rowInEdit = -1;    }    else    {       // clear the edit       fruitInEdit = null;       rowInEdit = -1;    } } 
image from book

The NewRowNeeded event handler fires when you move your cursor into the "*" row, which will create an empty Fruit object that can be edited.

This CellValuePushed event handler fires when you leave a cell, and this handler is responsible for saving a cell value into the appropriate property of the Fruit object that is being edited. This code tests to see if you have just started to edit an existing row, and if you have, the existing row is cloned. The clone is copied over the existing Fruit object when you leave the row, but if you press the Esc key twice, you will be able to cancel all column edits on the row, and the clone will be discarded without being committed.

The RowValidated event handler is used to commit an edited Fruit object to the fruitList collection. If the edited Fruit object was an existing Fruit object, the old Fruit object is overwritten. If the edited Fruit object is a new Fruit object, it's added to the fruitList collection.

If you run this code, adding rows and editing rows will appear to work, but more work is needed. To be able to delete rows and cancel edits, you must implement the UserDeletingRow event and the CancelRowEdit event, as shown in the following code.

image from book

Visual Basic

Private Sub DataGridView1_UserDeletingRow( _          ByVal sender As System.Object, _          ByVal e As _          System.Windows.Forms.DataGridViewRowCancelEventArgs) _          Handles DataGridView1.UserDeletingRow    If e.Row.Index < fruitList.Count Then       ' If the user has deleted an existing row, remove the       ' corresponding Fruit object from the data cache.       fruitList.RemoveAt(e.Row.Index)    End If    If e.Row.Index = Me.rowInEdit Then       ' If the user has deleted a newly created row,       ' simply release the corresponding Fruit object.       rowInEdit = -1       fruitInEdit = Nothing    End If End Sub Private Sub DataGridView1_CancelRowEdit(_          ByVal sender As System.Object, _          ByVal e As System.Windows.Forms.QuestionEventArgs) _          Handles DataGridView1.CancelRowEdit    If rowInEdit = DataGridView1.Rows.Count-2And_          rowInEdit = fruitList.Count Then       ' If user canceled the edit of a new row,       ' replace the corresponding Fruit object       ' with a new empty Fruit.       fruitInEdit = New Fruit("", 0, 0)    Else       ' If user cancels existing row edit,       ' release the edited Fuit object.       fruitInEdit = Nothing       rowInEdit = -1    End If End Sub 
image from book

image from book

C#

private void DataGridView1_UserDeletingRow(object sender,       DataGridViewRowCancelEventArgs e) {    if (e.Row.Index < fruitList.Count)    {       // If the user has deleted an existing row, remove the       // corresponding Fruit object from the data cache.       fruitList.RemoveAt(e.Row.Index);    }    if (e.Row.Index == rowInEdit)    {       // If the user has deleted a newly created row,       // simply release the corresponding Fruit object.       rowInEdit = -1;       fruitInEdit = null;    } } private void DataGridView1_CancelRowEdit(object sender,    QuestionEventArgs e) {    if ((rowInEdit == DataGridView1.Rows.Count - 2) &&        (rowInEdit == fruitList.Count))    {       // If user canceled the edit of a new row,       // replace the corresponding Fruit object       // with a new empty Fruit.       fruitInEdit = new Fruit("", 0, 0);    }    else    {       // If user cancels existing row edit,       // release the edited Fuit object.       fruitInEdit = null;       rowInEdit = -1;    } } 
image from book

You should now have a functioning DataGridView object that has virtual mode enabled and implemented. You might want to implement other events to expand the functionality of this example, but the example should give you a good idea of what is required to get an editable DataGridView object to operate in virtual mode.

 


Programming Microsoft ADO. NET 2.0 Applications. Advanced Topics
Linux Application Development (2nd Edition)
ISBN: 735621411
EAN: 2147483647
Year: 2004
Pages: 85
Authors: Michael K. Johnson, Erik W. Troan
BUY ON AMAZON

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