Click here to Skip to main content
15,861,340 members
Articles / Desktop Programming / WPF
Article

ListBox Drag & Drop using Attached Properties

Rate me:
Please Sign up or sign in to vote.
4.92/5 (10 votes)
28 Jan 2008CPOL2 min read 86.3K   3.7K   45   6
Enabling drag & drop between Listboxes using Attached Properties (In only 1 line of code)

Notes

A more detailed explanation of attached properties can be found here.

Introduction

After finishing with the GlassEffect article, I started dreaming of more ways to use Attached Properties. What better way than to use it to enable drag & drop on Listbox controls?

The DragAndDrop class will be provided by me with two attached properties: DragEnabled to enable dragging on a ListBox and DropEnabled to enable dropping on a ListBox. This can be applied to the same ListBox or on multiple ListBox controls. To enable dragging, just add the following one line of code:

XML
<ListBox x:Name="SourceListBox" src:DragAndDrop.DragEnabled="true"/> 

and the following to allow dropping:

XML
<ListBox x:Name="DestinationListBox" src:DragAndDrop.DropEnabled="true"/>  

Drag & Drop

Drag & Drop in WPF is relatively easy to implement. Here are the definitions of the two attached properties:

C#
#region DragEnabled 
public static readonly DependencyProperty DragEnabledProperty = 
DependencyProperty.RegisterAttached("DragEnabled", typeof(Boolean), 
    typeof(DragAndDrop), new FrameworkPropertyMetadata(OnDragEnabledChanged)); 

public static void SetDragEnabled(DependencyObject element, Boolean value) 
{ 
    element.SetValue(DragEnabledProperty, value); 
}

public static Boolean GetDragEnabled(DependencyObject element) 
{ 
    return (Boolean)element.GetValue(DragEnabledProperty); 
} 

public static void OnDragEnabledChanged
    (DependencyObject obj, DependencyPropertyChangedEventArgs args) 
{ 
    if ((bool)args.NewValue == true) 
    { 
        ListBox listbox = (ListBox)obj; 
        listbox.PreviewMouseLeftButtonDown += 
            new MouseButtonEventHandler(listbox_PreviewMouseLeftButtonDown);
    } 
} 
#endregion 

#region DropEnabled 
public static readonly DependencyProperty DropEnabledProperty = 
DependencyProperty.RegisterAttached("DropEnabled", typeof(Boolean), 
    typeof(DragAndDrop), new FrameworkPropertyMetadata(OnDropEnabledChanged)); 

public static void SetDropEnabled(DependencyObject element, Boolean value) 
{ 
    element.SetValue(DropEnabledProperty, value); 
} 

public static Boolean GetDropEnabled(DependencyObject element) 
{ 
    return (Boolean)element.GetValue(DropEnabledProperty); 
} 

public static void OnDropEnabledChanged
    (DependencyObject obj, DependencyPropertyChangedEventArgs args) 
{ 
    if ((bool)args.NewValue == true) 
    { 
        ListBox listbox = (ListBox)obj; 
        listbox.AllowDrop = true; 
        listbox.Drop += new DragEventHandler(listbox_Drop); 
    } 
} 
#endregion

It is important to notice that to enable dragging, we add a handler to the PreviewMouseLeftButtonDown and to enable dropping of items, we set AllowDrop to true and add a handler for the Drop event.

Before we implement the event handlers, let's also look at two other important parts of this class. First let's look at the helper class we use to determine the item being dragged based on the point where the mouse is clicked. We use the provided hit testing here. This code is taken from here:

C#
#region Helper 
private static object GetObjectDataFromPoint(ListBox source, Point point) 
{ 
    UIElement element = source.InputHitTest(point) as UIElement; 
    if (element != null) 
    { 
        object data = DependencyProperty.UnsetValue; 
        while (data == DependencyProperty.UnsetValue) 
        { 
            data = source.ItemContainerGenerator.ItemFromContainer(element); 
            if (data == DependencyProperty.UnsetValue) 
                element = VisualTreeHelper.GetParent(element) as UIElement; 
                if (element == source) 
                    return null; 
        } 
        if (data != DependencyProperty.UnsetValue) 
            return data; 
    } 
    return null; 
} 
#endregion 

We also have two static objects you need to know about. Once the mouse is clicked over an item, I keep a reference of its ListBox and I also remember the type of its ListBoxItem's content. Why do I do this? ListBoxItems can contain two types of items: the ListBox can contain UIElements (like TextBlock, Image, etc.) or it can be bound to a data structure (CLR objects, XML data, etc.). These need to be handled differently, thus we need to know its type! I will explain later how each type is handled...

Next, let's look at the handling to enable the dragging:

C#
static void listbox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
{ 
    DragSource = (ListBox)sender; 
    object data = (object)GetObjectDataFromPoint(DragSource, e.GetPosition(DragSource)); 
    if (data != null) 
    {
        DragType = data.GetType(); 
        DragDrop.DoDragDrop(DragSource, data, DragDropEffects.Copy); 
    } 

We set the DragSource to the sending ListBox. Next, use the GetObjectDataFromPoint helper function to determine if we are dragging a valid item. Also store the type of the dragged item. Lastly we call DragDrop.DoDragDrop. This handles the rest of the work for us!

C#
static void listbox_Drop(object sender, DragEventArgs e)
{
    object data = e.Data.GetData(DragType);
    if (DragType.IsVisible == true)
        DragSource.Items.Remove(data);
    ((ListBox)sender).Items.Add(data);
}

This is where the real action happens. When an item is dropped, this event is invoked. It returns the item that was dropped. This is where we need to know what the type of the item is that gets dropped. If it was a UIElement, then we will receive a type equal to typeof(ListBoxItem). We need to remove it from the source or else we get an exception! All that is left now is to add the dropped data into our destination ListBox!

Attached properties are great! It gives us a clean and concise way of enabling functionality on existing controls and as always... it can be turned on using only one line of code!!!

History

  • 11 January 2008: Initial release

Links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
South Africa South Africa
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionQuestion Pin
Member 111864202-Nov-14 5:41
Member 111864202-Nov-14 5:41 
QuestionHow Does It Works? Pin
Nanung5-Jul-10 17:34
Nanung5-Jul-10 17:34 
GeneralListbox with checkboxes Pin
Mika Lavie25-Jan-10 6:20
Mika Lavie25-Jan-10 6:20 
GeneralWindow.DragMove() is never called in your sample app Pin
fjparisIII3-Oct-09 6:41
fjparisIII3-Oct-09 6:41 
GeneralGreat! Pin
nmarcel2-Apr-08 17:22
nmarcel2-Apr-08 17:22 
GeneralVery nice Pin
Sacha Barber11-Jan-08 22:38
Sacha Barber11-Jan-08 22:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.