Introduction
While doing some recent home-work, I needed to figure out how to implement drag-and-drop from a DataGridView control. I had two main controls on the form: a ListBox that contained a list of categories, and a DataGridView control that contained a list of products associated with that category. To keep things simple, I wanted to be able to drag a product from the Grid to the ListBox to change a product's category.
Starting the Drag-and-Drop
The first thing I did was to attach an event handler to the DataGridView's MouseDown event. Because the DataGridView monopolizes the left mouse button, and it is very efficient at performing updates, inserts, and deletes, I decided to use the right mouse button for drag-and-dropping. I didn't need context menus. (Otherwise, I would have considering RMB + Alt/Shift/Ctrl clicking).
void grdCampaigns_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
DataGridView.HitTestInfo info = grdCampaigns.HitTest(e.X, e.Y);
if (info.RowIndex >= 0)
{
DataRowView view = (DataRowView)
grdCampaigns.Rows[info.RowIndex].DataBoundItem;
if (view != null)
grdCampaigns.DoDragDrop(view, DragDropEffects.Copy);
}
}
}
The code tests for the right mouse Button. If the RMB was pressed, I performed a HitTest using the x and y components of the MouseEventArgs. Because the RMB does not natively interact with the DataGridView, clicking the RMB will not select a row. If the user clicks the fourth row, and the first row was selected (from before), they will unwittingly drag the first row, rather than the fourth row. It was not intuitive at all.
So I used the information returned by HitTest to ensure I was working with the row and the column under the mouse pointer, rather than the previously selected row, which made the program much more intuitive.
After checking to make sure the data was valid, I passed the DataRowView into the DoDragDrop event, which started the drag-and-drop operation.
Preparing for the Drop
All the above preparation is useless without having a target to drop the information in. To that end, you need to prepare a drop target. If you are working with standard data types, this may not be necessary. Some support may be there already. This is especially true of strings. For more complex data types, however, such as a DataRowView (with which I was working), you will need to provide the plumbing yourself.
This is actually rather easy. First, you need to tell the target it is available for drag-and-drop operations. This is done by setting the AllowDrop property of most controls to true. Secondly, you need to add code to the DragEnter event of the control.
void lstCategories_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
You can use whatever effect you want, but it should match the effect you used in the DoDragDrop method called earlier, when starting the drag. When I tried drag-and-drop without this line of code, it did not work.
Implementing the Drop
The last step is to implement the DragDrop event of the target control, and manipulate the data.
void lstCategories_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(DataRowView)))
{
Point p = lstCategories.PointToClient(new Point(e.X,e.Y));
int index = lstCategories.IndexFromPoint(p);
if (index >= 0)
{
DataRowView category = (DataRowView)lstCategories.Items[index];
DataRowView campaign = (DataRowView)
e.Data.GetData(typeof(DataRowView));
int newID = (int)category[0];
int oldID = (int)campaign[1];
if (oldID != newID)
{
campaign.BeginEdit();
campaign[1] = newID;
campaign.EndEdit();
}
}
}
}
The first step I performed in the drag-drop was to ensure the type of data being dropped was the type I expected to receive. This can be done through the GetDataPresent method, which accepts as its parameter a data type, and returns true if that type is present.
Once I was sure I had valid data--or at least the right data type--I got screen coordinates where the DragDrop operation ended. Like the DataGridView control before it, I had no idea where the data was going. The user could drag the product on any visible category: I had to figure out which one.
That was accomplished through the IndexFromPoint method. However, documentation on this particular method is very poor, and Microsoft fails to note that you need CLIENT coordinates for this to work, not SCREEN coordinates. So, before you can call IndexFromPoint, you need to convert coordinates from screen to client coordinates using the appropriately named PointToClient method.
Once I determined (and verified) the category, I cast references to data items for both the category and the product (campaign). To save unnecessary processing, I checked to make sure the category was in fact different.
I should point out at this time that I was working with a strongly-typed DataSet, with two tables: Categories and Campaigns. Both contained the column CategoryID; CategoryID is the primary key of Categories, and was used for reference in Campaigns. The ListBox used in the examples was bound to the Categories table in the dataset. Whenever the Categories list selection changed, a DataView of child rows was retrieved from the DataSet, and bound to the DataGridView control.
The significance of this point is that when I edit the CategoryID of the campaign DataRowView in the example above, the associated product listing is removed from the grid, because the CategoryID no longer qualifies the product to be in the grid (it is no longer a child of the current category). When the user clicks on the category to which the product was moved, however, the product will appear in that listing, instead.
All in all, the code performed exactly what I wanted: it was simple, it worked, and it was intuitive for the user. I doubt I could have duplicated the results with less code or time any other way.
| You must Sign In to use this message board. |
|
|
 |
|
|
 |
|
 |
I had a small inspiration on drag dropping multiple items on a left mouse. I give this to the world!
As most find out, when you mouse down, just after the mouse down event, the control mouse processing decides you are hitting, before you can do the mouse move to test for drag/drop.
It dawned on me there was a simple hack to resolve this.
1. In the class declare a list, and create it at an appropriate time:
List drag_rows = new List();
2. Populate it on suspect MouseDowns
private void MyDataGridView_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) { drag_rows.Clear(); foreach (DataGridViewRow row in ResponsesSelector.SelectedRows) drag_rows.Add(row); StoreMouseDown(e.X, e.Y); } }
3. Reselect the items on the start of dragging
private void MyDataGridView_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { if (DragTriggered(e.X,e.y)) {
foreach (DataGridViewRow row in drag_rows) { row.Selected = true; my_dragDropList.Add(new my_DragDropEntry((string)row.Cells["DataOfInterest'].Value); } MyDataGridView.DoDragDrop(my_dragDropList, DragDropEffects.Copy); } }
}
In other words, store the selection in a list and then reselect, remembering that the list is just references to the original row.
There is a minor flicker of the selection on the mouse move, but the visual effect during dragging is correct.
Caveat: beware that you clear the list or you'll have a stack of rows building up in memory due to them remaining referenced, but I'd assume that in general storing rows over a mouse interaction is fairly safe.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Thank you for the very sueful code. Can you plese tell me how to drag one column(s) from one datagridview to another datagridview.
Thank you pappan
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
 |
Hi: In your code,you attach an event handler to the DataGridView's MouseDown event. void grdCampaigns_MouseDown(object sender, MouseEventArgs e) { }
if use this code, when you click different datagridview cells,the pragram responds quite slower because of the copy data operation. So I try this way
private void dataGridView2_MouseMove(object sender, MouseEventArgs e) { if (e.Button != MouseButtons.Left) return; DataGridView.HitTestInfo info = dataGridView2.HitTest(e.X, e.Y); if (info.RowIndex >= 0) { dataGridView2.DoDragDrop(dataGridView2.Rows[info.RowIndex], DragDropEffects.Copy); } }
then my program was running fluency 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I would appreciate a copy of source code in VB.net, if anyone has been able to convert successfully! Thanks in advance.
|
| Sign In·View Thread·PermaLink | 1.67/5 |
|
|
|
 |
|
|
 |
|
 |
Why in the world would you want drag and drop to only work with the Right mouse click. Users are bad enough if you provide controls that are the consistent way of doing things, yet alone when they have to remember in this program I have to click the right mouse button but in every other application I click the left mouse button.
Mike Lasseter
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
hello could anyone help me? please if anyone know how to make find & replace method in C# ? end me the code please and if anyone know how to solve the screan resolution problem in C# application? tell me how because when the resolution changes the form contents won`t arrange and thier size changes also thanks in advance bye
noha
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
How we can resize the datagridview rows and columns if we have drag and drop while on the Mouse left button click
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hi,
The statement abount DataGridView 'monopolizing' the left mouse button doesn't hold, here's some code from my DataGridView-derived class that does all the dirty work:
private Point ltmbpoint = Point.Empty; private bool lmbdown = false; private DataGridViewRow dragRow = null; protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button == MouseButtons.Left) { ltmbpoint = e.Location; lmbdown = true;
DataGridView.HitTestInfo info = this.HitTest(e.X, e.Y); if (info.RowIndex >= 0) { dragRow = this.Rows[info.RowIndex]; // Calling base.OnMouseDown() makes dragRow selected } else dragRow = null; } }
protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (e.Button == MouseButtons.Left) lmbdown = false; }
protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e);
// Filter out false move events and start drag&drop operation if (e.Location != ltmbpoint && lmbdown && dragRow != null) { // Send the selected DataGridViewRow DoDragDrop(dragRow, DragDropEffects.All); lmbdown = false; } }
protected override void OnQueryContinueDrag(QueryContinueDragEventArgs qcdevent) { base.OnQueryContinueDrag(qcdevent);
// Ends drag&drop on right mouse button | escape key if (((qcdevent.KeyState & 2) > 0) || qcdevent.EscapePressed) qcdevent.Action = DragAction.Cancel; }
protected override void OnDragOver(DragEventArgs drgevent) { base.OnDragOver(drgevent);
// Dissallow dropping a row onto its siblings if (drgevent.Data.GetDataPresent("System.Windows.Forms.DataGridViewRow")) drgevent.Effect = DragDropEffects.None; // Do whatever to allow droping from somewhere else }
And there you go. The class & her methods are a lot more complex, but I've tried to filter out only the question-related stuff from the overrides. IMHO, it should work, feel free to mail me, however, if it doesn't. Dragging with your RMB seems quite unnatural to me, I can't get it how someone can live with cancelling the drag&drop operation with only the ESC key (and not the RMB as well).
Best of luck with the code...
Janko Jerinic, janko.jerinic@gmail.com
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Sorry, but it doesn't work. It has the same problem that any other DataGridView drag&drop code I've seen by now has: The selection is always reduced to the row that was clicked on to start dragging. Dragging multiple selected rows is not possible with it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Why don't you use DataGridViewSelectedRows to determine the user current selection? This way you can not only drag one row per click, but also choose to select and transfer multiple rows at once. You wouldn't even have to perform hittests against the datagridview using mouse positions.
Also, instead of using the right button (which is also inintuitive) why don't you monitor Control.ModiferKeys for a key being hold, like the Alt key? This way you can have both drag'n drop functionality and contextmenus working.
But so thanks for your article, it was both very informative and useful.
Best wishes,
César Souza
|
| Sign In·View Thread·PermaLink | 2.25/5 |
|
|
|
 |
|
 |
Hi, Renzee,
Thanks for your example! Did you do this with WPF DataGridView control or just with the one of Win Form? We are also making drag-and-drop features but met an big issue with right mouse button upon the WPF ListView control.
Josh Smith is also facing the same problem. You can refer to his example: http://www.codeproject.com/useritems/ListViewDragDropManager.asp.
Will you give some hint or help on this issue?
Michael.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
 | Thanks  Josh Smith | 11:55 19 Jan '07 |
|
 |
Thanks for posting this article. It made my life very easy today.
:josh: My WPF Blog[ ^] We are what we repeatedly do. Excellence then, is not an act, but a habit. - Aristotle
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
That's a very good example for developers are new to drag'n'drop. Easy to understand and exactly what I searched for.
Thanks... 
Bye
Frank Loizzi Germany
|
| Sign In·View Thread·PermaLink | 3.00/5 |
|
|
|
 |
|
 |
Is it possible to rotate datagridview by 90 degrees? That means, is it possible to swap columns and rows in datagridview
Dejan
|
| Sign In·View Thread·PermaLink | 1.20/5 |
|
|
|
 |
|
 |
I want to use this code in my web application.but it generates error that "The type or namespace name 'MouseEventArgs' could not be found (are you missing a using directive or an assembly reference?" i cannot use system.windows.form bcause its a web application.so what is solution
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
Unfortunately, there is no solution. Web controls do not support drag and drop operations. The sample code was written for a C# Windows Application.
|
| Sign In·View Thread·PermaLink | 2.60/5 |
|
|
|
 |
|
|