Accordion in Windows Forms DataGridView

This is not really hard to do. The best approach would be to create a dedicated UserControl and overlay it at the right spot.

To make room for it simply change the Row’s Height, keeping track of the row, so you can revert it when it looses selection.

You will also have to decide if more than one row can be expanded and just what the user is supposed to do to expand and to reset the row..

The UserControl could have a function displayRowData(DataGridViewRow row) which you could call to display the fields you are interested in in its Labels etc..

You should also have a plan on how the Buttons shall interact with the DataGridView..

If you only want one Row to be expanded at a time, you can create the UC up front, make the DGV its Parent and hide it. Later upon the user interaction, like clicking at the row, or a certain cell you would move it to the right row and show it..

If more than one row can be expanded you will need to create several UCs and keep track of them in a List..

Here is a minimal example to encourage you..:

enter image description here

int normalRowHeight = -1;
UcRowDisplay display = new UcRowDisplay();
DataGridViewRow selectedRow = null;

public Form1()
{
    InitializeComponent();

    // create one display object
    display = new UcRowDisplay();
    display.Visible = false;
    display.Parent = DGV;
    display.button1Action = someAction;
}

After you have filled the DGV

    // store the normal row height
    normalRowHeight = DGV.Rows[0].Height;

You need at least these events:

private void DGV_SelectionChanged(object sender, EventArgs e)
{
    if (selectedRow != null) selectedRow.Height = normalRowHeight;
    if (DGV.SelectedRows.Count <= 0)
    {
        selectedRow = null;
        display.Hide();
        return;
    }
    // assuming multiselect = false
    selectedRow = DGV.SelectedRows[0];
    // assuming ColumnHeader show with the same height as the rows
    int y = (selectedRow.Index + 1) * normalRowHeight;
    display.Location = new Point(1, y);
    // filling out the whole width of the DGV.
    // maybe you need more, if the DGV is scrolling horizontally
    // or less if you show a vertical scrollbar.. 
    display.Width = DGV.ClientSize.Width;
    // make room for the display object
    selectedRow.Height = display.Height;
    // tell it to display our row data
    display.displayRowData(selectedRow);
    // show the display
    display.Show();
}

private void DGV_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
{
    // enforce refresh on the display
    display.Refresh();
}

Here is a test action triggered by the button in the display object:

public void someAction(DataGridViewRow row)
{
    Console.WriteLine(row.Index + "  " + row.Cells[2].Value.ToString());
}

And of course you need a UserControl. Here is a simple one, with two Labels, one Button and one extra Label to close the display:

public partial class UcRowDisplay : UserControl
{
    public UcRowDisplay()
    {
        InitializeComponent();
    }

    public delegate void someActionDelegate(DataGridViewRow row);
    public someActionDelegate button1Action { get; set; } 
    DataGridViewRow  myRow = null;

    public void displayRowData(DataGridViewRow row)
    {
        myRow  = row;
        label1.Text = row.Cells[1].Value.ToString();
        label2.Text = row.Cells[0].Value.ToString();
        rowDisplayBtn1.Text = row.Cells[2].Value.ToString();
    }

    private void rowDisplayBtn1_Click(object sender, EventArgs e)
    {
        button1Action(myRow);
    }

    private void label_X_Click(object sender, EventArgs e)
    {
        myRow.Selected = false;
        this.Hide();
    } 
}

It contains tthree Labels and a Button. In the Designer it looks like this:

enter image description here

Note that in order to make it a little easier on me I have modified the DGV to

  • have no RowHeader; modify the location if you have one.
  • assumed the column header to have the same height as a row.
  • all (normal) rows to have the same height.
  • set the DGV to multiselect=false and read-only

Update

Here is a quick example for scrolling:

private void DGV_Scroll(object sender, ScrollEventArgs e)
{
    DGV.PerformLayout();
    var ccr = DGV.GetCellDisplayRectangle(0, selectedRow.Index, true);
    display.Top = ccr.Top + normalRowHeight;  // **
    display.Visible = (ccr.Top >= 0 && ccr.Height > 0);
}

This (**) assumes the that the selected row shall remain visible. The same calculation should happen in the SelectionChanged event! It is up to you to decide what should happen when horizontal scrolling occurs..

Note that similar updates are necessary when rows are added or removed before the current row. So it should be moved to a function you call on those occurrances..

Leave a Comment