问题
I need to implement some kind of accordion effect in a Windows Form DataGridView. When the user selects a row, the row is expanded to display some more information and if possible some buttons or other controls. The problem is, I have absolutely no clue, how to do this. I tried to search the web, but I found nothing that can lead me in the right direction in creating this. I hope someone can tell me how to do this? (does not have to be a code sample)
I created the below mockup to display what I want to do.
I thought about adjusting the columns height and override the OnPaint method. I only need to display some text in the first version. If that is possible that would be great. I know that in the future I would need to place some buttons or other controls to do various things with the selected item. If that is very complicated to achieve, I will skip that part for now. I know I could use tooltips for text and have button columns etc. but in my case, I need to do it as an accordion.
Best regards Hans Milling…
回答1:
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 the 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..:

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:

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..
来源:https://stackoverflow.com/questions/29005397/accordion-in-windows-forms-datagridview