Saturday, September 27, 2014

Resize form to match the contents of DataGridView

Sometimes the sole purpose of a Form is to display a DataGridView. In that case, you probably want the Form to automatically resize to the size of the contents in the DataGridView. I've seen solutions that loop through all the rows and add up the height, but this is ugly, and usually does not take into account margins, padding, DividerHeight and row header padding. There must be a better way...

My strategy is to temporarily undock the DataGridView, set AutoSize to true, then capture the DataGridView's Size at that point, then restore the Dock and AutoSize property. Then use the captured size to resize the Winform:

// Within the Form class
private void AutoSizeFormToDataGridView()
 Size contentsSize = GetDataGridViewContentsSize();
 this.ClientSize = contentsSize;

protected Size GetDataGridViewContentsSize()
 DockStyle dockStyleSave = dataGridView1.Dock;
 dataGridView1.Dock = DockStyle.None;
 dataGridView1.AutoSize = true;
 Size dataContentsSize = dataGridView1.Size;
 dataGridView1.AutoSize = false;
 dataGridView1.Dock = dockStyleSave;
 return dataContentsSize;

Or alternatively you can define this as an extension method:

public static Size GetContentsSize(this DataGridView dataGrid) { //...


Friday, September 26, 2014

DataTable or DataGridView to CSV or HTML file using Clipboard

It turns out that DataGridView.GetClipboardContent() returns all the selected cells of a DataGridView as a type DataObject, which is conveniently consumed by the Windows.Forms.Clipboard class, as well as other WYSIWYG editors from Microsoft. From this you can set the Clipboard, then get the clipboard various data formats, including:
- Comma separated value
- Tab separated value

So instead of looping though columns and then rows, you can output the entire DataGridView as a CSV file in just 3 lines of code! (4 if you count setting the ClipboardCopyMode, which can be set in the Form Builder.

Here is the code:

void DataGridViewToCSV(string Filename)
   bool allowAddRows = dataGridView1.AllowUserToAddRows;
   bool rowHeadersVisible = dataGridView1.RowHeadersVisible;
   dataGridView1.AllowUserToAddRows = false;
   dataGridView1.RowHeadersVisible = false;

   // Choose whether to write header. You will want to do this for a CSV file.
   dataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
   // Select the cells we want to serialize.
   dataGridView1.SelectAll(); // One could also use DataGridView.Rows[RowIndex].Selected = true;

   // Save the current state of the clipboard so we can restore it after we are done
   IDataObject objectSave = Clipboard.GetDataObject();
   // Copy (set clipboard)
   // Paste (get the clipboard and serialize it to a file)
   // Restore the current state of the clipboard so the effect is seamless
   if(objectSave != null)
   dataGridView1.AllowUserToAddRows = allowAddRows;
   dataGridView1.RowHeadersVisible = rowHeadersVisible;

Some improvements

For a tab-delimited file, use the TextDataFormat.Text enum in your call to Clipboard.GetText(). You can also output your DataGridView as HTML by using TextDataFormat.Html instead of TextDataFormat.CommaSeparatedValue, but there is extra header data you have to parse out:

   string result = Clipboard.GetText(TextDataFormat.CommaSeparatedValue);
   result = result.Substring( result.IndexOf("") );

- An object must be serializable for it to be put on the Clipboard.