Accordion in Windows Forms Datagridview

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..:

Sample Image

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:

Sample Image

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..

Extending DataGridViewRow to have container span under columns

TaW's comments solved the issue:

Yes, this is just an example for starters. You will want to code the Scroll event to adapt the placement of the UC. Other event include adding or deleting Rows before the current one etc.. Note that DGC rows and cells are only virtual, ie they are not controls and have only a limited or rather a very specialized event model

I want to develop my own Dat Grid View for Win Form

DataGridView implementation spans thousands LOC.
Have a look for yourself https://github.com/dotnet/winforms/blob/master/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.cs

It is not something you want to reimplement yourself.

Using a custom UserControl as a Column in a DataGridView

You want to use a UserControl as a Column in a DataGridView. To display scores/rows/columns of your UserControl ('yuc') there are several options. Here are three that come to my mind:

  • Drop the DGV and go for a FlowLayoutPanel. This is simple to implement and will work pretty much out of the box. The only con is that the performance will get sluggish if you have too many controls in total in it. Let's assume your UC (yuc) has 10 controls; a few thousand controls are the limit in WinForms, so a few (100-300) yucs will work ok, but beyond that you need to rethink the design.

  • Go all the way and create a specialized DataGridView Cell that will host your yuc. Here is a complete and nice walk-through. As you can see this will amount to quite a lot of extra work; classes to add, interfaces to implement etc.. And, what is worse: All this is really meant to make the new cell type act like a regular DGV cell, read it will hold and allow you to edit only one value. That's a lot less than what your yuc probably can do..

  • Option 3: Cheat! You can combine the avantages of yuc data and DGV display performance if you display only one yuc in the current cell by overlaying it and make all other cells display what their yucs would look like.

This third option will allow you to add pretty much as many rows as your memory allows, although it is worth mentioning that the total column widths can't exceed 64k.

I can think of two ways to create the right display: The cells could display a Bitmap they hold along with their other data in a Tag structure or they could paint them in the CellPaint event. The former takes more memory but should work faster.

You need to create a data class yucData for your yuc that holds all data needed to initalze a yuc. It could also hold a Bitmap a yuc can create using the DrawToBitmap method.

Now each time the current cell is moved you show/move the editing yuc and initialize it to the data in the cell's Tag. When the values have changed you update the Bitmap.

And in the CellPainting event you draw the Bitmap into each cell.

See here for an example of overlaying a UserControl onto a DataGridView. There it overlays a whole row, which grows accordion-like to hold all its size.. The other rows and cells are quite regular..

What I wrote is about rows but you can just as well put them into columns you create dynamically.

But all this is really only worth it if you hit the limit of controls in Winforms with a FLP.

Well, if WPF is an option, there all this will not be an issue..

Using tag in datagridview

Tag is a very common .Net property, so the question is bit unclear. But looking at the image and taking a wild guess on what you may want..:

If you want to have the ability to add Labels, let's call them TagLabels during runtime you may want to use a FlowLayoutPanel as their container. It will allow adding more and will take care of the layout no matter what sizes they have.

Example:
Sample Image

To create them we can use a TextBox, which we add to the FLP first. Then we code its PreviewKeyDown event and let the user create a new TagLabel by pressing enter..:

private void textBox1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
if (e.KeyCode == Keys.Enter && textBox1.Text != "")
{
Label lbl = new Label {
Text = " " + textBox1.Text, /* some room for the image */
BorderStyle = BorderStyle.Fixed3D,
TextAlign = ContentAlignment.MiddleCenter,
AutoSize = true,
Margin = new Padding(2),
ImageIndex = flowLayoutPanel1.Controls.Count %
imageList1.Images.Count,
ImageList = imageList1,
ImageAlign = ContentAlignment.MiddleLeft,
MinimumSize = new Size(100, 20),
BackColor = Color.LightGoldenrodYellow,
Name = "TagLabel" + (flowLayoutPanel1.Controls.Count)
};
lbl.MouseClick +=lbl_MouseClick ;
flowLayoutPanel1.Controls.Add(lbl);
flowLayoutPanel1.Controls.SetChildIndex(lbl,
flowLayoutPanel1.Controls.Count - 2);
textBox1.Text = "";
}
else
if (e.KeyCode == Keys.Escape)
{
textBox1.Text = "";
}
}

The Click event should be generic for all labels; so we first cast sender to Label and can then code the processing..:

private void lbl_MouseClick(object sender, MouseEventArgs e)
{
Label lbl = sender as Label;
//...
MessageBox.Show(lbl.Name + " : Ouch! You clicked on " + lbl.Text.Trim());
}

This is just a basic piece of code. You can style the labels any way you want and of course also include code for deleting, maybe with a context menu. If those labels shall carry more responsibility, you can and should create a class, probably a Label subclass to hold further data and methods..

I'm also using an ImageList to display images to the left. Do change these details to suit your needs!


Note that there is no reasonable way to add the FLP to an ordinary DataGridView. You may be able to workaround but depending on your needs it may be best to keep them separate. DGVs have Cells and while these can hold special controls this is complicated and will always be restricted by the cells' i.e. the Columns' and the Rows' Size. As an alternative you can check out this example to see how you can insert virtual space to a row to hold a control but neither DGV nor its Cells are containers.

Add an expander (collapse/expand) to a Panel WinForm

There is another WinForms Expander : http://jfblier.wordpress.com/2011/02/16/window-form-expander/



Related Topics



Leave a reply



Submit