|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionIn another community someone asked whether it is possible to implement something like drag&drop inside a ListView. The person claimed to have searched for an example but didn't find any. Only articles dealing with dragging items into or out of a ListView, but not dragging and dropping items within the same ListView. This question raised my interest so I built a quick&dirty solution to try it out. After the basics worked I added a few "bells and whistles" (like showing the insertion point in the ListView), but we'll come to this later on. Basic IdeaEach drag and drop operation is mainly just a combination of handling three events:
Implementation - first legThe first thing we have to do is to remember which item is being dragged: // The LVItem being dragged private ListViewItem _itemDnD = null; I chose a Also remember, that Like stated above, to achieve our goal we have to handle the following events of our MouseDownThe _itemDnD = listView1.GetItemAt(e.X, e.Y); // if the LV is still empty, no item will be found anyway, so we don't have to consider this caseNot much else to do here - just remember which item was clicked on and you're done. MouseMoveOnce the user actually moves the mouse while having pressed a mouse button, the actual drag operation is performed. if (_itemDnD == null) return; // Show the user that a drag operation is happening Cursor = Cursors.Hand; For simplicity reasons I just chose one of the predefined For a start, that's sufficient as well. We'll give additional feedback later on. MouseUpNow we're at the point where the actual work is done. The user has released the mouse button, so we have to find out where the item has been dropped. The if (_itemDnD == null) return; // use 0 instead of e.X so that you don't have to keep inside the columns while dragging ListViewItem itemOver = listView1.GetItemAt(0, e.Y); if (itemOver == null) return; listView1.Items.Remove(_itemDnD); listView1.Items.Insert(itemOver.Index, _itemDnD); Cursor = Cursors.Default; The only problem is that this algorithm isn't sophisticated enough to decide whether to insert before or after the item we dropped our There's also a problem now when we drop the item on itself: First it's being removed from the Don't worry, we'll fix this later on. First summaryApart from the few flaws I mentioned above we've already implemented drag and drop operations within a Now on to the "bells and whistles" I mentioned earlier: Implementation - second legInsertion before/after an itemRight now we always insert before the item we released the mouse button over, so it's not possible to make an item become the last item of the ListView. To improve the handling I chose to find out whether the item has been dropped on the upper or lower half of the target item. If it's been dropped on the upper half, we'll insert before the target item, otherwise after the target item. Finding out where exactly the mouse has been released is being accomplished with the help of // use 0 instead of e.X so that you don't have to keep inside the columns while dragging ListViewItem itemOver = listView1.GetItemAt(0, e.Y); if (itemOver == null) return; Rectangle rc = itemOver.GetBounds(ItemBoundsPortion.Entire); // find out if we insert before or after the item the mouse is over bool insertBefore; if (e.Y < rc.Top + (rc.Height / 2)) insertBefore = true; else insertBefore = false; if (_itemDnD != itemOver) // if we dropped the item on itself, nothing is to be done { if (insertBefore) { listView1.Items.Remove(_itemDnD); listView1.Items.Insert(itemOver.Index, _itemDnD); } else { listView1.Items.Remove(_itemDnD); listView1.Items.Insert(itemOver.Index + 1, _itemDnD); } } Please note that I also checked whether the item is being dropped on itself. In this case we don't have to do anything. With this modification we can place the item before the first or after the last Giving feedback by extending the ListViewNow that we can decide where exactly to insert the item, a little more feedback would be nice, don't you think? I was thinking of drawing a colored line where the item will be inserted if you release the button. But how to achieve this goal? We can find out whether to insert before or after the current item in the MouseMove event, just like we already do in MouseUp, so that's not the problem. What is a little complicated, though, is how to perform the actual drawing onto the ListView. Usually, to perform some extra painting on a control, you can derive from the control, override Unfortunately, the ListView doesn't call Nevertheless, there is a way to accomplish custom painting on the ListView. The overridden method in a ListView derived class looks like this: protected override void WndProc(ref Message m) { base.WndProc(ref m); if (m.Msg == WM_PAINT) { if (LineBefore >= 0 && LineBefore < Items.Count) { Rectangle rc = Items[LineBefore].GetBounds(ItemBoundsPortion.Entire); DrawInsertionLine(rc.Left, rc.Right, rc.Top); } if (LineAfter >= 0 && LineBefore < Items.Count) { Rectangle rc = Items[LineAfter].GetBounds(ItemBoundsPortion.Entire); DrawInsertionLine(rc.Left, rc.Right, rc.Bottom); } } }
The painting of the insertion line has been encapsulated in a private method Now that the SummaryI hope I could show you how drag and drop operations inside a ListView can be accomplished with a few simple steps. The basic task isn't very hard, but as soon as you want to implement a little more sophisticated control, you should know a little bit about message handling in .NET (and Windows) controls. History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||