Dev Q&A

DataGridView

Edited by Nancy Michell

After receiving a late-breaking news bulletin at magazine headquarters early this month stating that the Web is just a fad that will never amount to anything, we have reluctantly decided to retire our venerable Web Q&A column and replace it with one we like to call Dev Q&A. Seriously, Web programming has grown far beyond HTML-formatted text to encompass just about any kind of programming you can imagine. Limiting the scope of this column to Web-only questions seems a little superficial. Dev Q&A will take a look at questions related to many aspects of programming in Visual Basic®, C#, ASP.NET, and the like. Compiled from questions submitted internally and answered by Microsoft developers, the column will present useful solutions to commonly encountered coding dilemmas. This month we'll discuss the Windows® Forms DataGridView control.

Q How do I get a DataGridView to scroll programmatically to a given row index? I have tried to define the following helper in my grid class derived from DataGridView:

public void ScrollToRow ( int rowIndex )
{
  OnScroll(new ScrollEventArgs(
    ScrollEventType.LargeIncrement, rowIndex));
}

However, it does not work. I'm using the Microsoft® .NET Framework 2.0 Beta 2.

Q How do I get a DataGridView to scroll programmatically to a given row index? I have tried to define the following helper in my grid class derived from DataGridView:

public void ScrollToRow ( int rowIndex )
{
  OnScroll(new ScrollEventArgs(
    ScrollEventType.LargeIncrement, rowIndex));
}

However, it does not work. I'm using the Microsoft® .NET Framework 2.0 Beta 2.

A There are a number of properties and methods that can be used in order to scroll the rows or columns programmatically. Among others, they include FirstDisplayedScrollingColumnIndex, FirstDisplayedScrollingRowIndex, FirstDisplayedScrollingColumnHiddenWidth, HorizontalScrollingOffset, and VerticalScrollingOffset. The DataGridView control also exposes the VerticalScrollBar and HorizontalScrollBar protected properties, which you can use in order to display a particular row or column and control the built-in scrollbars.

A There are a number of properties and methods that can be used in order to scroll the rows or columns programmatically. Among others, they include FirstDisplayedScrollingColumnIndex, FirstDisplayedScrollingRowIndex, FirstDisplayedScrollingColumnHiddenWidth, HorizontalScrollingOffset, and VerticalScrollingOffset. The DataGridView control also exposes the VerticalScrollBar and HorizontalScrollBar protected properties, which you can use in order to display a particular row or column and control the built-in scrollbars.

Q I am trying to add rows dynamically to a Windows Forms DataGrid bound to an ArrayList, but the new row is not displayed in the DataGrid control. How can I show the newly appended row? Here's my code:

private void addButton_Click(object sender, System.EventArgs e)
{
  XmlAttribute attribute = GetAttributeToAdd();
  this.attributeArray.Add(attribute);
 
  this.attributeDataGrid.DataBindings.Clear();
  this.attributeDataGrid.DataSource = attributeArray;
  this.attributeDataGrid.SetDataBinding(this.attributeArray, null);
  this.attributeDataGrid.Refresh();
}

Q I am trying to add rows dynamically to a Windows Forms DataGrid bound to an ArrayList, but the new row is not displayed in the DataGrid control. How can I show the newly appended row? Here's my code:

private void addButton_Click(object sender, System.EventArgs e)
{
  XmlAttribute attribute = GetAttributeToAdd();
  this.attributeArray.Add(attribute);
 
  this.attributeDataGrid.DataBindings.Clear();
  this.attributeDataGrid.DataSource = attributeArray;
  this.attributeDataGrid.SetDataBinding(this.attributeArray, null);
  this.attributeDataGrid.Refresh();
}

A You need to wrap the code that adds items to the array list in a currencyManager.SuspendBinding/ResumeBinding block. By the way, you don't need to set the Data source on the DataGrid each time you add an item. Without that, the added code is a bit simpler, as you see here:

// PRECONDITION: the DataGrid's Data source is already set.
private void addButton_Click(object sender, System.EventArgs e)
{
  this.BindingContext[this.attributeDataGrid.DataSource, 
    this.attributeDataGrid.DataMember].SuspendBinding();
 
  XmlAttribute attribute = GetAttributeToAdd();
  this.attributeArray.Add(attribute);
 
  this.BindingContext[this.attributeDataGrid.DataSource, 
    this.attributeDataGrid.DataMember].ResumeBinding();
}

A You need to wrap the code that adds items to the array list in a currencyManager.SuspendBinding/ResumeBinding block. By the way, you don't need to set the Data source on the DataGrid each time you add an item. Without that, the added code is a bit simpler, as you see here:

// PRECONDITION: the DataGrid's Data source is already set.
private void addButton_Click(object sender, System.EventArgs e)
{
  this.BindingContext[this.attributeDataGrid.DataSource, 
    this.attributeDataGrid.DataMember].SuspendBinding();
 
  XmlAttribute attribute = GetAttributeToAdd();
  this.attributeArray.Add(attribute);
 
  this.BindingContext[this.attributeDataGrid.DataSource, 
    this.attributeDataGrid.DataMember].ResumeBinding();
}

Note that in the .NET Framework 2.0, there are better data binding mechanisms than ArrayList, such as BindingList<T>. BindingList<T> is a generic implementation of IBindingList and is very useful for data binding.

Q I have a DataGridView problem: when dragging column headers to different locations, the column header flickers. I derive from the DataGridView to provide grouping support, but I have no custom logic for handling column moves. Can you help?

Q I have a DataGridView problem: when dragging column headers to different locations, the column header flickers. I derive from the DataGridView to provide grouping support, but I have no custom logic for handling column moves. Can you help?

A In your derived class, have you set DoubleBuffer to be true? Doing so could cause flickering when moving columns around. To fix this, you could turn off DoubleBuffer or write code for custom painting when moving columns.

A In your derived class, have you set DoubleBuffer to be true? Doing so could cause flickering when moving columns around. To fix this, you could turn off DoubleBuffer or write code for custom painting when moving columns.

A better solution, so you don't have to write code for custom painting or sacrifice the advantages of double buffering, is shown in Figure 1. The code in this solution looks to see if you are on the column header and, if so, turns off DoubleBuffer, but only while manipulating the column headers.

Figure 1 Eliminating Flicker While Moving Columns

protected override void OnMouseDown(MouseEventArgs e)
{
    base.OnMouseDown(e); 

    DataGridView.HitTestInfo hitTestInfo = this.HitTest(e.X, e.Y);
    if (hitTestInfo.Type == DataGridViewHitTestType.ColumnHeader)
    {
        this.DoubleBuffered = false;
    }
}

protected override void OnMouseUp(MouseEventArgs e)
{
    base.OnMouseUp(e);
    if (!this.DoubleBuffered)
    {
        this.DoubleBuffered = true;
    }
}

Q In my Windows-based application, I have a DataGridView. A BindingList<BusinessObject> contains my business objects, and I've assigned it as the grid's data source. Now, sorting does not work. Do you have a solution?

Q In my Windows-based application, I have a DataGridView. A BindingList<BusinessObject> contains my business objects, and I've assigned it as the grid's data source. Now, sorting does not work. Do you have a solution?

A There are two ways to sort with the DataGridView: one is in an unbound DataGridView, where you can use the SortCompare method. The other is if you're bound to a list that implements sorting. The BindingList has hooks so that you can support sorting and searching, but neither of these is built in. To support sorting, you should inherit a new list type from BindingList<BusinessObject> and override the sorting methods/properties (there are several, including SupportsSortingCore, IsSortedCore, SortPropertyCore, SortDirectionCore, ApplySortCore, and RemoveSortCore).

A There are two ways to sort with the DataGridView: one is in an unbound DataGridView, where you can use the SortCompare method. The other is if you're bound to a list that implements sorting. The BindingList has hooks so that you can support sorting and searching, but neither of these is built in. To support sorting, you should inherit a new list type from BindingList<BusinessObject> and override the sorting methods/properties (there are several, including SupportsSortingCore, IsSortedCore, SortPropertyCore, SortDirectionCore, ApplySortCore, and RemoveSortCore).

Q Do you know of a property that will allow a user to type a new value into a DataGridView ComboBox?

Q Do you know of a property that will allow a user to type a new value into a DataGridView ComboBox?

A There is no property to enable this for a DataGridView ComboBox, but you can write some code yourself to do it. First, you need to handle the EditingControlShowing event in order to change the DropDownStyle of the ComboBox control to DropDown. Then, in the CellValidating event, add the FormattedValue value to the ComboBox list if it doesn't already exist. This has been implemented in Figure 2.

A There is no property to enable this for a DataGridView ComboBox, but you can write some code yourself to do it. First, you need to handle the EditingControlShowing event in order to change the DropDownStyle of the ComboBox control to DropDown. Then, in the CellValidating event, add the FormattedValue value to the ComboBox list if it doesn't already exist. This has been implemented in Figure 2.

Figure 2 Interacting with the DataGridView ComboBox

private void dataGridView1_EditingControlShowing(
    object sender, DataGridViewEditingControlShowingEventArgs e)
{
    ComboBox c = e.Control as ComboBox;
    if (c != null) c.DropDownStyle = ComboBoxStyle.DropDown;
}

private void dataGridView1_CellValidating(
    object sender, DataGridViewCellValidatingEventArgs e)
{
    if (e.ColumnIndex == comboBoxColumn.Index)
    {
        object eFV = e.FormattedValue;
        if (!comboBoxColumn.Items.Contains(eFV))
        {
            comboBoxColumn.Items.Add(eFV);
        }
    }
}

Q Do ComboBox controls get reused across columns, or does each column get its own ComboBox editing control?

Q Do ComboBox controls get reused across columns, or does each column get its own ComboBox editing control?

A ComboBox controls can be reused across columns. The editing control type is cached and if the types are the same, then the control is reused. However, a lot of properties are reset in the process (including DropDownStyle).

A ComboBox controls can be reused across columns. The editing control type is cached and if the types are the same, then the control is reused. However, a lot of properties are reset in the process (including DropDownStyle).

Q I've used the columns editor in Visual Studio 2005 to define the set of columns that I'd like to show in the DataGridView, but it displays all of the properties, not just the ones I define. How do I limit the display to just the ones I want?

Q I've used the columns editor in Visual Studio 2005 to define the set of columns that I'd like to show in the DataGridView, but it displays all of the properties, not just the ones I define. How do I limit the display to just the ones I want?

A By default, at design time the designer adds all public properties it finds that aren't attributed as Browsable(false). You can then use the columns editor to modify the column set (add/remove).

A By default, at design time the designer adds all public properties it finds that aren't attributed as Browsable(false). You can then use the columns editor to modify the column set (add/remove).

At run time you can set the AutoGenerateColumns property to false and manually add columns (using the DataGridView.Columns.Add method) to the DataGridView. You set the column's DataPropertyName to identify if the column is bound and what field it is bound to.

Q Can you tell me the best way to bind XML data to a DataGridView control?

Q Can you tell me the best way to bind XML data to a DataGridView control?

A The simplest possible solution is to load the XML into a DataSet and bind to that. Another solution would be to use XML serialization to create an object graph from the XML and bind to that. To bind to the raw XML, you would need to create wrapper classes that implement ITypedList, IBindingList, and ICustomTypeDescriptor.

A The simplest possible solution is to load the XML into a DataSet and bind to that. Another solution would be to use XML serialization to create an object graph from the XML and bind to that. To bind to the raw XML, you would need to create wrapper classes that implement ITypedList, IBindingList, and ICustomTypeDescriptor.

Q In a DataGridView with columns of type DataGridViewComboBoxColumn, if the user selects a new value in the cell using the ComboBox dropdown, a new row is automatically added to the end of the grid if there are no more empty rows. I need the user to be able to type inside the cells, so I handle the EditingControlShowing event and change the DropDownStyle of the ComboBox. However, the new row is not added if the user does not use the dropdown to enter the data.

Q In a DataGridView with columns of type DataGridViewComboBoxColumn, if the user selects a new value in the cell using the ComboBox dropdown, a new row is automatically added to the end of the grid if there are no more empty rows. I need the user to be able to type inside the cells, so I handle the EditingControlShowing event and change the DropDownStyle of the ComboBox. However, the new row is not added if the user does not use the dropdown to enter the data.

A This behavior is by design. The grid doesn't know that any value has changed, since it only listens to the SelectedIndexChanged event. As shown in Figure 3, you can manually listen to the ComboBox's TextChanged event and then call the DataGridView's NotifyCurrentCellDirty method so that the new row gets added the moment the user types in the new value.

A This behavior is by design. The grid doesn't know that any value has changed, since it only listens to the SelectedIndexChanged event. As shown in Figure 3, you can manually listen to the ComboBox's TextChanged event and then call the DataGridView's NotifyCurrentCellDirty method so that the new row gets added the moment the user types in the new value.

Figure 3 Using NotifyCurrentCellDirty

this.m_dataGridView.EditingControlShowing += 
    new DataGridViewEditingControlShowingEventHandler(
        this.m_dataGridView_EditingControlShowing);

void m_dataGridView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    ComboBox comboBox = e.Control as ComboBox;
    if (comboBox != null)
    {
        comboBox.DropDownStyle = ComboBoxStyle.DropDown;
        comboBox.TextChanged += new EventHandler(comboBox_TextChanged);
    }
}

void comboBox_TextChanged(object sender, EventArgs e)
{
    this.m_dataGridView.NotifyCurrentCellDirty(true);
}

Q I have switched recently to the .NET Framework 2.0. I am now considering using DataGridView instead of DataGrid in my app. I need a checkbox column that has checked and unchecked states, and a disabled state. How can I do this?

Q I have switched recently to the .NET Framework 2.0. I am now considering using DataGridView instead of DataGrid in my app. I need a checkbox column that has checked and unchecked states, and a disabled state. How can I do this?

A The DataGridView does not have built-in support for disabled controls, so you will have to implement your own checkbox cell/column. The documentation includes a sample for creating a disabled button cell/column.

A The DataGridView does not have built-in support for disabled controls, so you will have to implement your own checkbox cell/column. The documentation includes a sample for creating a disabled button cell/column.

Q How do I make a ComboBox appear only for the row that has current focus in a DataGridView, not just for the cell that has focus?

Q How do I make a ComboBox appear only for the row that has current focus in a DataGridView, not just for the cell that has focus?

A You need to set the DataGridViewComboBoxColumn.DisplayStyle property to Nothing for all ComboBox columns. Finally, use the DataGridView RowEnter event and RowLeave events to toggle the DataGridViewComboBoxCell.DisplayStyle property from Nothing to ComboBox for the specific row. Figure 4 shows an example that assumes the ComboBox column is the first column.

A You need to set the DataGridViewComboBoxColumn.DisplayStyle property to Nothing for all ComboBox columns. Finally, use the DataGridView RowEnter event and RowLeave events to toggle the DataGridViewComboBoxCell.DisplayStyle property from Nothing to ComboBox for the specific row. Figure 4 shows an example that assumes the ComboBox column is the first column.

Figure 4 Showing a ComboBox for the Current Row

private void dataGridView1_RowEnter(
    object sender, DataGridViewCellEventArgs e)
{
    DataGridViewComboBoxCell comboBoxCell = (DataGridViewComboBoxCell) 
        this.dataGridView1.Rows[e.RowIndex].Cells[0];
    comboBoxCell.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox;
}

private void dataGridView1_RowLeave(
    object sender, DataGridViewCellEventArgs e)
{
    DataGridViewComboBoxCell comboBoxCell = (DataGridViewComboBoxCell)
        this.dataGridView1.Rows[e.RowIndex].Cells[0];
    comboBoxCell.DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing;
}

Q I need to display a specific set of images in a cell based upon the integer value of the cell. For example, when the cell's value is three, I need three dots to appear in the cell. How do I do this?

Q I need to display a specific set of images in a cell based upon the integer value of the cell. For example, when the cell's value is three, I need three dots to appear in the cell. How do I do this?

A You'll need to create a new cell class that inherits from the DataGridViewImageCell. By default, the DataGridViewImageCell's formatted value and real value types are both images. In your custom image cell you need to set the ValueType to be integer (since your values are integers, not images). Next you need to override the GetFormattedValue property where you provide an integer to image mapping. This code can be as complex as necessary, but you should avoid it being slow, since the formatted value is used many times during painting.

A You'll need to create a new cell class that inherits from the DataGridViewImageCell. By default, the DataGridViewImageCell's formatted value and real value types are both images. In your custom image cell you need to set the ValueType to be integer (since your values are integers, not images). Next you need to override the GetFormattedValue property where you provide an integer to image mapping. This code can be as complex as necessary, but you should avoid it being slow, since the formatted value is used many times during painting.

In your case you can create three static images that contain dots for each integer value you want. In the GetFormattedValue code, you will return the correct image based upon the integer. Finally, if the grid's AllowUserToAddRows is true then you need to override the DefaultNewRowValue property and return a default integer value. This is necessary because the default DataGridViewImageCell returns a red "X" image, but you need the default value to be an integer. Creating a custom image column as well will make it easy to use your custom image cell. An example is shown in Figure 5.

Figure 5 Custom Image Column for the DataGridView

public class CustomImageColumn : DataGridViewImageColumn
{
    public CustomImageColumn()
    {
        this.CellTemplate = new CustomImageCell();
        this.ValueType = typeof(int);
    }
}

public class CustomImageCell : DataGridViewImageCell
{
    static Image [] dotImages = ... // load up custom dot images

    public CustomImageCell()
    {
        this.ValueType = typeof(int);
    }

    protected override object GetFormattedValue(
        object value, int rowIndex, ref DataGridViewCellStyle cellStyle, 
        TypeConverter valueTypeConverter, 
        TypeConverter formattedValueTypeConverter,
        DataGridViewDataErrorContexts context)
    {
        return dotImages[(int)value];
    }

    public override object DefaultNewRowValue
    {
        get { return 0; }
    }
}

Figure 5 Custom Image Column for the DataGridView

Thanks to the following Microsoft developers for their technical expertise: Harshit Agarwal, Abhinaba Basu, Scott Berry, Mark Boulter, Regis Brid, Mark Feinholz, Pankaj Garg, Daniel Herling, Kavitha Jonnakuti, Appireddy Kikkuru, Mark Rideout, Chris Sells, Ketaanh Shah, Gagandeep Singh, Saritha Suryadevara, Sunil Swami, and Vijaya Bhaskar Yerrapureddy.