I’m doing more Windows Forms programming these day and I am very surprised to see the lack of documentation on the web regarding DataGridView and copy and paste as well as drag and drop. Not even the DataGridView FAQ – an excellent resource – covers copy and paste or drag and drop other than the ClipboardCopyMode.
With a little code you can enhance the copy and paste as well as implement drag on drop for selected rows in a DataGridView. First thing, all users think differently. Some use toolbars. some use context menus. Some use window menus. I’ll leave the window menus to the reader.
If you have a toolbar or a binding navigator, add cut, copy, and paste icons to the bar. The easiest way I have found is to add a ToolStrip control, use the “add standard items” and then copy the three ToolStripButtons and delete the unneeded ToolStrip control.
Add a ContextMenuStrip and add Cut, Copy and Paste to it. Wiring up events is pretty easy.
private void cutToolStripMenuItem_Click(object sender, EventArgs e)
{
this.cutToClipboard();
}
private void copyToolStripMenuItem_Click(object sender, EventArgs e)
{
this.copyToClipboard();
}
private void pasteToolStripMenuItem_Click(object sender, EventArgs e)
{
this.pasteFromClipboard();
}
private void cutToolStripButton_Click(object sender, EventArgs e)
{
cutToClipboard();
}
private void copyToolStripButton_Click(object sender, EventArgs e)
{
copyToClipboard();
}
private void pasteToolStripButton_Click(object sender, EventArgs e)
{
pasteFromClipboard();
}
Implementing the actual methods is pretty easy. The trick is to use both DataGridView’s GetClipboardContent as well as your own DataFormat.
private DataFormats.Format employeeDataFormat = DataFormats.GetFormat(“employeeDataFormat”);
private void deleteCurrentSelection()
{
this.employeeBindingSource.RemoveCurrent();
}
private void cutToClipboard()
{
copyToClipboard();
deleteCurrentSelection();
}
private void copyToClipboard()
{
IDataObject clipboardData = getDataObject();
Clipboard.SetDataObject(clipboardData);
}
private IDataObject getDataObject()
{
//TODO: why does this return plaintext(tsv) and CSV that has empty first column?
DataObject clipboardData = this.dataGridView1.GetClipboardContent();
Employee[] employees =
new Employee[dataGridView1.SelectedRows.Count];
for (int i = 0; i < dataGridView1.SelectedRows.Count; i++)
{
employees[i] = dataGridView1.SelectedRows[i].DataBoundItem as Employee;
}
clipboardData.SetData(employeeDataFormat.Name, employees);
return clipboardData;
}
private void pasteEmployeeDataObject(IDataObject dataObject)
{
object employeeDataObject = dataObject.GetData(employeeDataFormat.Name);
if (employeeDataObject != null)
{
Employee[] employees = (Employee[])employeeDataObject;
foreach (Employee employee in employees)
{
bindingRowData.Add(employee);
}
}
}
private void pasteCsv(IDataObject dataObject)
{
object lDataObjectGetData = dataObject.GetData(DataFormats.CommaSeparatedValue);
string csv = lDataObjectGetData as string;
if (csv==null)
{
System.IO.MemoryStream stream = lDataObjectGetData as System.IO.MemoryStream;
if (stream!=null)
csv = new System.IO.StreamReader(stream).ReadToEnd();
}
if (csv==null)
return;
string[] lines = csv.Split(‘\n’);
int row = dataGridView1.NewRowIndex;
int col = dataGridView1.CurrentCell.ColumnIndex;
foreach (string line in lines)
{
if (row < dataGridView1.RowCount && line.Length > 0)
{
try
{
string[] cells = line.Split(‘,’);
for (int i = 0; i < cells.Length
; ++i)
{
if (col + i < this.dataGridView1.ColumnCount)
{
dataGridView1[col + i, row].Value =
Convert.ChangeType(cells[i], dataGridView1[col + i, row].ValueType);
}
else
{
break;
}
}
row++;
}
catch (FormatException)
{ //TODO: log exceptions using a nice standard logging library 🙂
dataGridView1.CancelEdit();
}
}
else
{
break;
}
}
}
private void pasteFromClipboard()
{
if (dataGridView1.SelectedRows.Contains(dataGridView1.Rows[dataGridView1.NewRowIndex]))
{
dataGridView1.Rows[dataGridView1.NewRowIndex].Selected = false;
dataGridView1.CancelEdit();
}
IDataObject clipboardData = Clipboard.GetDataObject();
if (Clipboard.ContainsData(employeeDataFormat.Name))
{
pasteEmployeeDataObject(clipboardData);
}
else if (Clipboard.ContainsData(DataFormats.CommaSeparatedValue))
{
pasteCsv(clipboardData);
}
}
The cool thing about the different paste methods and getObject method is that they work well with drag and drop as well.
Accepting drops is easy. It is totally straight forward just like it is with other windows forms controls.
private void dataGridView1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(employeeDataFormat.Name) || e.Data.GetDataPresent(DataFormats.CommaSeparatedValue))
{
e.Effect = DragDropEffects.Copy;
}
else
e.Effect = DragDropEffects.None;
}
private void dataGridView1_DragDrop(object sender, DragEventArgs e)
{
if (e.Effect == DragDropEffects.Copy)
{
string[] formats = e.Data.GetFormats();
if (e.Data.GetDataPresent(employeeDataFormat.Name))
{
pasteEmployeeDataObject(e.Data);
}
else if (e.Data.GetDataPresent(DataFormats.CommaSeparatedValue))
{
pasteCsv(e.Data);
}
}
}
Handling drags is a little more challenging. DataGridView doesn’t have the ItemDrag event. Total bummer. It turns out implementing drag detection is very easy with a simple user32.dll function call called – amazingly enough – DragDetect.
[System.Runtime.InteropServices.DllImport(“user32.dll”)]
private static extern bool DragDetect(IntPtr intPtr, Point point);
Point dragEnterPoint;
private void dataGridView1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.None)
{
this.dataGridView1.MouseMove -= new System.Windows.Forms.MouseEventHandler(this.dataGridView1_MouseMove);
}
DataGridView.HitTestInfo info = dataGridView1.HitTest(e.X, e.Y);
bool lDragDetect = DragDetect(this.Handle, dragEnterPoint);
if (lDragDetect)
{
if (info.RowIndex >= 0)
{
IDataObject dataObject = getDataObject();
if (dataObject != null)
dataGridView1.DoDragDrop(dataObject, DragDropEffects.Copy);
}
}
}
private void dataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (e.ColumnIndex >=0 )
{
dragEnterPoint = new Point(e.X, e.Y);
this.dataGridView1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dataGridView1_MouseMove);
}
}
}
private void dataGridView1_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
this.dataGridView1.MouseMove -= new System.Windows.Forms.MouseEventHandler(this.dataGridView1_MouseMove);
}
A couple things are going on here. We don’t want to change too much behavior of the DataGridView. Dragging in the RowHeader normally highlights multiple rows. So in the CellMouseDown handler we check that ColumnIndex is non-negative. ColumnIndex is -1 if the mouse was down in the RowHeader.
I’m not using the DataGridView Click or MouseDown events because they would prevent those events from getting down to the cells and prevent normal cell editing behavior. The MouseMove event is used to call DragDetect but the event is only registered and fired if the mouse button is down. I don’t want to be firing lots of pointless MouseMove events. This works well enough.
Finally DoDragDrop is called with the same getObject result as a copy uses.
Once catch. DataGridView does implement its own copy if you press ctrl-c. This means that the current row gets put onto the clipboard, but it isn’t using my copy method. What is worse is that if my copy method is used, then I can do something really cool like copy, then reorder the columns and paste. Since it is using the custom DataFormat and just adding to a databound list, everything pastes into the proper columns. The alternative is that things paste into the wrong columns or worse, you get an exception because the datatypes don’t match. Oddly, DataGridView doesn’t implement paste via ctrl-v. This is trivially rectified.
private void dataGridView1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.C && e.Control)
{
this.copyToClipboard();
e.Handled = true; //otherwise the control itself tries to “copy”
}
if (e.KeyCode==Keys.v && e.Control)
{
this.pasteFromClipboard();
}
}
Overall the result is nice. You can copy and drag rows within the control itself, between multiple instances of the same DataGrid. You can drag and copy and paste between Excel. Its all good.