Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Use ListView control for editing cell in DataGridView

4.00/5 (1 vote)
15 May 2013CPOL2 min read 30.9K   1.8K  
Sample of how to make a listview popup for editing a cell in a DataGridView.

Image 1

Introduction

I was searching long and hard to find a good article on how to get a GridViewCell in a DataGridView to display a ListView panel when editing column, similar to the one found here: http://msdn.microsoft.com/en-us/library/7tas5c80.aspx.  

I ended up having to make it myself from scratch. At first I thought it would be a quite straight forward job, but I was proven wrong. Since I normally benefit from what others add to Code Project, it is now my time to give something back. 

The sample code is quite simple, but it shows you how to implement a ListView working similar to the date picker from Microsoft.   

Background

The reason for making this was a need for a better representation than what was available in a drop-list. I also want the possibility to edit values while selecting value. For this reason I wanted an advanced controller when editing a cell. In lack of any good samples, I decided to make one. 

Using the code 

Making the control

As with the  DateTimePicker sample from Microsoft, I've made derived classes from DataGridViewColumn and DataGridViewTextBoxCell to get my own control when editing the cell.  

In addition I've overriden the function PositionEditingControl in my cell to position the control where I want it.  

C#
public override void PositionEditingControl(bool setLocation, bool setSize, 
   Rectangle cellBounds, Rectangle cellClip, DataGridViewCellStyle cellStyle, 
   bool singleVerticalBorderAdded, bool singleHorizontalBorderAdded, 
   bool isFirstDisplayedColumn, bool isFirstDisplayedRow)
{
    // Set position and size of control area to get
    // list view placed "outside" of datagridview.
    Size
        size = new Size(cellBounds.Width + 100, 100);
    Point
        location = new Point(cellBounds.Location.X, cellBounds.Location.Y + cellBounds.Height);

    // If control goes outside dialog, move control position.
    if ((location.Y + size.Height) > this.DataGridView.TopLevelControl.ClientSize.Height)
        location.Y -= (size.Height + cellBounds.Height);
    if ((location.X + size.Width) > this.DataGridView.TopLevelControl.ClientSize.Width)
        location.X -= 100;

    Rectangle
        ctlSize = new Rectangle(location, size);
    base.PositionEditingControl(setLocation, setSize, ctlSize, ctlSize, cellStyle, 
      singleVerticalBorderAdded, singleHorizontalBorderAdded, 
      isFirstDisplayedColumn, isFirstDisplayedRow);

    if (m_control != null)
    {
        location.Offset(DataGridView.Location);
        m_control.Location = location;
    }
}   

Getting value from control  

I've chosen to fetch value from control when control is detached from the cell.  

C#
public override void DetachEditingControl()
{
    …
    CustomViewControl ctl = DataGridView.EditingControl as CustomViewControl;
    if (ctl != null && ctl.SelectedItems.Count > 0)
    {
        this.Value = ctl.SelectedItems[0].SubItems[0].ToString();
        ctl.EditingControlFormattedValue = String.Empty;
    }
} 

Then i listen to the CellEndEdit event on my DataGridView and fetch the value from the cell there.  

C#
private void m_gridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
    DataGridView
        gridView = sender as DataGridView;
    if (gridView != null)
    {
        DataGridViewCell
            cell = gridView.CurrentCell as CustomViewCell;
        if (cell != null && cell.Value != null)
        {
            string sValue = cell.Value as string;
        }
    }
} 

Placing the user defined control outside of DataGridView 

To get the ListView placed above the DataGridView, not being limited by the borders of the DataGridView, I add the control to the top level control, and remove it when I'm done editing.  

C#
protected override void OnCreateControl()
{
    base.OnCreateControl();
    // Need to add controll to parent ui to get it floating outside of gridview
    Form1
        topLevel = this.TopLevelControl as Form1;
    if (topLevel != null && this.Parent != topLevel)
    {
        topLevel.Controls.Add(this);
        m_dataGridView.Controls.Remove(this);
        this.BringToFront();
    }
  }
}

protected override void OnLeave(EventArgs e)
{
    // Notify the DataGridView that the contents of the cell
    // have changed.
    base.OnLeave(e);
    if (m_dataGridView.IsCurrentCellInEditMode && !m_boEnded)
        m_dataGridView.EndEdit(); // Ending edit twice will make the program crash.

    // Remove control from parent ui after edit is done.
    Form1
        parent = this.Parent as Form1;
    if (parent != null)
    {
        parent.Controls.Remove(this);
        m_dataGridView.ClearSelection();
    }
}  

Issues 

In my case I want to use different inherited listviews on different columns. I tried making a column control where I use generics to specify what kind of controller I want to use. To do this I made a control that inherits from Panel, implements IDataGridViewEditingControl and adds the desired control to that panel. This looked promising, but there are some issues to it. The sample contains the halfway functioning generics control. If anyone know how to get it working, then please tell me how. 

If this is solved, than it will be possible to make any panel the control used when editing a DataGridViewCell.  

While the normal control is created every time a cell is edited and deleted when edit is ended, when adding the control as an on the panel, some of the basic handling from windows fails.  

Issues to this solution are: 

  • Flickering when activating control 
  • Closing control when moving outside of window   

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)