Click here to Skip to main content
Click here to Skip to main content

ListBox Drag & Drop using Attached Properties

, 28 Jan 2008
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

rudigrobler

South Africa South Africa
No Biography provided

Comments and Discussions

 
GeneralVery nice PinmvpSacha Barber11-Jan-08 22:38 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 28 Jan 2008
Article Copyright 2008 by rudigrobler
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid