Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ListBox Drag & Drop using Attached Properties

0.00/5 (No votes)
28 Jan 2008 1  
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:

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

and the following to allow dropping:

<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:

#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:

#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:

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!

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here