Drag and Drop

The basic Drag and Drop functionality allows user to move data between two controls: drag source and drop target. Regular .NET ListView does support this basic Drag and Drop functionality, but not any additional (and often pleasant) features based on Drag and Drop.

Better ListView adds these features:

Setting up Better ListView as a Drag Source

Three properties are essential for setting up Better ListView as a drag source:

Item Reordering

Item reordering is enabled by setting ItemReorderMode property to either Enabled or Custom. The difference between these values is that on Enabled Better ListView does all the item reordering automatically (items will be actually reordered in the list), but Custom shows only the effect, fires item reorder events and the actual reordering is expected to be done externally. This is practical - for example - when one displays confirmation dialog to proceed reordering or just needs to deny the reordering in some cases.

This image shows item reordering in action:

Drag Effects

There are two effect available while dragging over Better ListView.

DropHighlight looks like a hot state of an item. It shows when user is dragging data over an item. It is useful when data are to be dropped on a certain item:

InsertionMark is a line showing target position between neighboring items. It is useful for inserting data in the list or item reordering:

When the InsertionMark effect is turned on, the insertion mark is updated when mouse drags over the control. You may need to set custom location of insertion mark in certain situations. In that case, set the BetterListViewDragDropEffectSettingEventArgs.UpdateInsertionMark property to false in the DragDropEffectSetting event handler.

Internal and External Drop Display

There are two properties governing the effect during Drag and Drop operation: ItemDropDisplayInternal and ItemDropDisplayExternal.

Internal drop display is used when source and target of the drag-drop operation is the same. This effect is InsertionMark by default and refers to item reordering.

External drop display is used when data are dragged from another control. This effect is DropHighlight by default. DropHighlight can be disabled on selected items by setting AllowDropHighlight property to false.

Customized Drop Display on Specific Items

If you need to show different effects on specific items, you can use properties BetterListViewItem.CustomDropDisplayInternal and BetterListViewItem.CustomDropDisplayExternal. Their values are initially set to BetterListViewDragDropDisplay.Default, which means that the values are derived from ItemDropDisplayInternal and ItemDropDisplayExternal properties instead (see Internal and External Drop Display).

Default Format of Dragged Data

Items can be dragged between different Better ListViews, other controls and even across application domains (between applications). Better ListView uses its own structure of type BetterListViewItemDragData.

BetterListViewItemDragData contains both dragged item indices and cloned items. Items are cloned because when the dragged data crosses application domain, it is serialized and must not contain any reference to source Better ListView since such objects cannot be transferred through this channel. For the purpose of indentifying drag source control, BetterListViewItemDragData contains globally-unique ID of the Better ListView (BetterListView.DragSourceID property).

Getting Insertion Location

When doing item reordering, insertion location is available in CheckItemReorder even data (BetterListViewCheckItemReorderEventArgs.InsertionLocation) so that you can decide whether to disable insertion mark or do other action depending on current insertion location.

During Drag and Drop, however, there is no such data readily available. The same functionality can be still achieved using GetDropInfo method. This method return insertion location depending on current drag effect setting (drop highlight, insertion mark). The parameters are screen coordinates of mouse cursor position (these are readily available in the DragDropEffectSetting event handler, which you can use during Drag and Drop operation).

Sample Source Code

C#

//
// setup the first Better ListView
//
this.listView1.BeginUpdate();

this.listView1.Items.AddRange(
    new[]
    {
        "A Fork",
        "A Spoon",
        "A Knife"
    });

this.listView1.AutoSizeItemsInDetailsView = true;

this.listView1.EndUpdate();

// allow dragging items from this list
this.listView1.AllowDrag = true;
// allow dropping items on this list
this.listView1.AllowDrop = true;
// show insertion mark when dragging over this list
this.listView1.ItemDropDisplayExternal = BetterListViewDragDropDisplay.InsertionMark;

// this event has to be handled to properly set some effect (e.g. 'Move') when dragging
this.listView1.DragDropEffectSetting += ListViewDragDropEffectSetting;
// this event tells us that user dropped data on some item of the Better ListView (DragDrop event is more general than this)
this.listView1.ItemDrop += ListViewItemDrop;
// handle the case when user drops item(s) on empty list - in that case ItemDrop is not raised and we should use DragDrop
this.listView1.DragDrop += ListViewDragDrop;

//
// setup the second Better ListView
//
this.listView2.BeginUpdate();

this.listView2.Items.AddRange(
    new[]
    {
        "A Chair",
        "A Table",
        "A Wardrobe"
    });

this.listView2.AllowDrag = true;
this.listView2.AllowDrop = true;
this.listView2.ItemDropDisplayExternal = BetterListViewDragDropDisplay.InsertionMark;

this.listView2.AutoSizeItemsInDetailsView = true;

this.listView2.EndUpdate();

this.listView2.DragDropEffectSetting += ListViewDragDropEffectSetting;
this.listView2.ItemDrop += ListViewItemDrop;
this.listView2.DragDrop += ListViewDragDrop;

Visual Basic

'
' setup the first Better ListView
'
ListView1.BeginUpdate()

ListView1.Items.AddRange(
    New String() {
        "A Fork",
        "A Spoon",
        "A Knife"
                 })

ListView1.AutoSizeItemsInDetailsView = True

ListView1.EndUpdate()

' allow dragging items from this list
ListView1.AllowDrag = True
' allow dropping items on this list
ListView1.AllowDrop = True
' show insertion mark when dragging over this list
ListView1.ItemDropDisplayExternal = BetterListViewDragDropDisplay.InsertionMark

' this event has to be handled to properly set some effect (e.g. 'Move') when dragging
AddHandler ListView1.DragDropEffectSetting, AddressOf ListViewDragDropEffectSetting
' this event tells us that user dropped data on some item of the Better ListView (DragDrop event is more general than this)
AddHandler ListView1.ItemDrop, AddressOf ListViewItemDrop
' handle the case when user drops item(s) on empty list - in that case ItemDrop is not raised and we should use DragDrop
AddHandler ListView1.DragDrop, AddressOf ListViewDragDrop

'
' setup the second Better ListView
'
ListView2.BeginUpdate()

ListView2.Items.AddRange(
    New String() {
        "A Chair",
        "A Table",
        "A Wardrobe"
                 })

ListView2.AllowDrag = True
ListView2.AllowDrop = True
ListView2.ItemDropDisplayExternal = BetterListViewDragDropDisplay.InsertionMark

ListView2.AutoSizeItemsInDetailsView = True

ListView2.EndUpdate()

AddHandler ListView2.DragDropEffectSetting, AddressOf ListViewDragDropEffectSetting
AddHandler ListView2.ItemDrop, AddressOf ListViewItemDrop
AddHandler ListView2.DragDrop, AddressOf ListViewDragDrop

Source code for the ListViewDragDropEffectSetting event:

C#

void ListViewDragDropEffectSetting(object sender, BetterListViewDragDropEffectSettingEventArgs eventArgs)
{
    BetterListViewItemDragData itemDragData = (BetterListViewItemDragData)eventArgs.Data.GetData(typeof(BetterListViewItemDragData));

    Control child = GetChildAtPoint(PointToClient(new Point(eventArgs.X, eventArgs.Y)));

    if (child is BetterListView &&
        ((BetterListView)child).DragSourceID == itemDragData.DragSourceID) // check whether the data comes from this cotnrol
    {
        // do not allow dropping on the source control
        eventArgs.Effect = DragDropEffects.None;
    }
    else
    {
        eventArgs.Effect = DragDropEffects.Move;
    }
}

Visual Basic

Sub ListViewDragDropEffectSetting(ByVal sender As Object, ByVal eventArgs As BetterListViewDragDropEffectSettingEventArgs)

    Dim itemDragData As BetterListViewItemDragData = DirectCast(eventArgs.Data.GetData(GetType(BetterListViewItemDragData)), BetterListViewItemDragData)

    Dim child As Control = GetChildAtPoint(PointToClient(New Point(eventArgs.X, eventArgs.Y)))

    If TypeOf child Is BetterListView.BetterListView AndAlso DirectCast(child, BetterListView.BetterListView).DragSourceID = itemDragData.DragSourceID Then

        ' check whether the data comes from this cotnrol
        ' do not allow dropping on the source control
        eventArgs.Effect = DragDropEffects.None

    Else
        eventArgs.Effect = DragDropEffects.Move
    End If

End Sub

Source code for the ListViewItemDrop event:

C#

void ListViewItemDrop(object sender, BetterListViewItemDropEventArgs eventArgs)
{
    BetterListViewItemDragData itemDragData = (BetterListViewItemDragData)eventArgs.Data.GetData(typeof(BetterListViewItemDragData));
    
    BetterListView listViewSource = GetSourceList(itemDragData);
    BetterListView listViewTarget = GetTargetList(itemDragData);

    // remove items from the source list
    listViewSource.Items.RemoveRange(itemDragData.Items);

    // insert items to the target list (either before or after the target item, depending on the insertion location)
    listViewTarget.Items.InsertRange(
        (eventArgs.ItemDropPart == BetterListViewDropPart.After)
            ? (eventArgs.Item.Index + 1)
            : eventArgs.Item.Index,
        itemDragData.Items);
}

Visual Basic

Sub ListViewItemDrop(ByVal sender As Object, ByVal eventArgs As BetterListViewItemDropEventArgs)

    Dim itemDragData As BetterListViewItemDragData = DirectCast(eventArgs.Data.GetData(GetType(BetterListViewItemDragData)), BetterListViewItemDragData)

    Dim listViewSource As BetterListView.BetterListView = GetSourceList(itemDragData)
    Dim listViewTarget As BetterListView.BetterListView = GetTargetList(itemDragData)

    ' remove items from the source list
    listViewSource.Items.RemoveRange(itemDragData.Items)

    ' insert items to the target list (either before or after the target item, depending on the insertion location)
    listViewTarget.Items.InsertRange(If((eventArgs.ItemDropPart = BetterListViewDropPart.After), (eventArgs.Item.Index + 1), eventArgs.Item.Index), itemDragData.Items)

End Sub

Source code for the ListViewDragDrop event:

C#

void ListViewDragDrop(object sender, DragEventArgs e)
{
    BetterListViewItemDragData itemDragData = (BetterListViewItemDragData)e.Data.GetData(typeof(BetterListViewItemDragData));

    BetterListView listViewSource = GetSourceList(itemDragData);
    BetterListView listViewTarget = GetTargetList(itemDragData);

    if (listViewTarget.Items.Count == 0)
    {
        // remove items from the source list
        listViewSource.Items.RemoveRange(itemDragData.Items);

        // add items to the target list
        listViewTarget.Items.AddRange(itemDragData.Items);
    }
}

Visual Basic

Sub ListViewDragDrop(ByVal sender As Object, ByVal e As DragEventArgs)

    Dim itemDragData As BetterListViewItemDragData = DirectCast(e.Data.GetData(GetType(BetterListViewItemDragData)), BetterListViewItemDragData)

    Dim listViewSource As BetterListView.BetterListView = GetSourceList(itemDragData)
    Dim listViewTarget As BetterListView.BetterListView = GetTargetList(itemDragData)

    If listViewTarget.Items.Count = 0 Then

        ' remove items from the source list
        listViewSource.Items.RemoveRange(itemDragData.Items)

        ' add items to the target list
        listViewTarget.Items.AddRange(itemDragData.Items)

    End If

End Sub

Source code for the GetSourceList and GetTargetList methods:

C#

BetterListView GetSourceList(BetterListViewItemDragData itemDragData)
{
    return ((itemDragData.DragSourceID == this.listView1.DragSourceID) // check whether the data comes from the first ListView
                ? this.listView1
                : this.listView2);
}

BetterListView GetTargetList(BetterListViewItemDragData itemDragData)
{
    return ((itemDragData.DragSourceID == this.listView1.DragSourceID) // check whether the data comes from the first ListView
                ? this.listView2
                : this.listView1);
}

Visual Basic

Function GetSourceList(ByVal itemDragData As BetterListViewItemDragData) As BetterListView.BetterListView

    ' check whether the data comes from the first ListView
    Return (If((itemDragData.DragSourceID = ListView1.DragSourceID), ListView1, ListView2))

End Function

Function GetTargetList(ByVal itemDragData As BetterListViewItemDragData) As BetterListView.BetterListView

    ' check whether the data comes from the first ListView
    Return (If((itemDragData.DragSourceID = ListView1.DragSourceID), ListView2, ListView1))

End Function