MVVM and Drag and Drop Command Binding with an Attached Behavior

Drag and drop works quite well already with some UIElements, such as TextBox. However, you may want to using MVVM and binding to bind a command for dragging and dropping to a UIElement that doesn’t support drag and drop, such as a Grid or other container.

This can easily be done using an Attached Behavior. You may know what an Attached Property is and you may be wondering, what is the difference between an Attached Behavior and an Attached Property. An Attached Behavior is a type of an Attached Property that involves certain events, often user interaction events such a drag and drop.

Creating an Attached Behavior for use in WPF

So what is the basic recipe for creating an Attached Behavior.

  1. Use a public static class
  2. Add a static Dependency Property and and an associated PropertyChangedCallBack method (after testing, these can be private)
  3. Add a public method that is a static setter and getter (after testing the getter can be private)
  4. Add any static helper methods (usually these could be private)
  5. Add any additional code that is needed.

Handling Drag and Drop in WPF

If you are going to handle Drag and Drop in WPF, you need to make yourself acquainted with a particular static object called DataFormats (System.Windows.DataFormats, as there exists one in the Forms namespace too).   This object is going to help you. Microsoft says that the DataFormats class provides a set of predefined data format names that can be used to identify data formats available in the clipboard or drag-and-drop operations.[1You should take a moment to read about this class if you are not familiar with it. IDataObject and DataObject (which implements IDataObject) are also important to know and you should read about those as well.

Determine which types of data your need to handle with Drag and Drop. For each data type you plan to handle, you should check if the drag and drop data is that type and if so, handle it. You have the option to handle this in the code behind or in the behavior.

Also, with UIElements in WPF, the drag events are separate from the drop events, so we really don’t need a DragBehavior or DragAndDropBehavior, instead we only need a DropBehavior.[2]  You should read about the drag and drop events and understand which one you need to add a behavior too. We are going to use the PreviewDrop event.

Writing a DropBehavior

Ok, now that we have gained the knowledge we need, lets write a DropBavior static class.

  1. Create a new class and call it DropBehavior.
  2. Make the class both public and static.
  3. Add a DependecyProperty called PreviewDropCommandProperty (Lines 13 – 26).
  4. Create the Setter and Getter functions. (Lines 28-57)
  5. Create the PropertyChangedCallBack method. (Lines 59-78)
using System.Windows.Input;
using System.Windows;

namespace MVVM
{
    /// <summary>
    /// This is an Attached Behavior and is intended for use with
    /// XAML objects to enable binding a drag and drop event to
    /// an ICommand.
    /// </summary>
    public static class DropBehavior
    {
        #region The dependecy Property
        /// <summary>
        /// The Dependency property. To allow for Binding, a dependency
        /// property must be used.
        /// </summary>
        private static readonly DependencyProperty PreviewDropCommandProperty =
                    DependencyProperty.RegisterAttached
                    (
                        "PreviewDropCommand",
                        typeof(ICommand),
                        typeof(DropBehavior),
                        new PropertyMetadata(PreviewDropCommandPropertyChangedCallBack)
                    );
        #endregion

        #region The getter and setter
        /// <summary>
        /// The setter. This sets the value of the PreviewDropCommandProperty
        /// Dependency Property. It is expected that you use this only in XAML
        ///
        /// This appears in XAML with the "Set" stripped off.
        /// XAML usage:
        ///
        /// <Grid mvvm:DropBehavior.PreviewDropCommand="{Binding DropCommand}" />
        ///
        /// </summary>
        /// <param name="inUIElement">A UIElement object. In XAML this is automatically passed
        /// in, so you don't have to enter anything in XAML.</param>
        /// <param name="inCommand">An object that implements ICommand.</param>
        public static void SetPreviewDropCommand(this UIElement inUIElement, ICommand inCommand)
        {
            inUIElement.SetValue(PreviewDropCommandProperty, inCommand);
        }

        /// <summary>
        /// Gets the PreviewDropCommand assigned to the PreviewDropCommandProperty
        /// DependencyProperty. As this is only needed by this class, it is private.
        /// </summary>
        /// <param name="inUIElement">A UIElement object.</param>
        /// <returns>An object that implements ICommand.</returns>
        private static ICommand GetPreviewDropCommand(UIElement inUIElement)
        {
            return (ICommand)inUIElement.GetValue(PreviewDropCommandProperty);
        }
        #endregion

        #region The PropertyChangedCallBack method
        /// <summary>
        /// The OnCommandChanged method. This event handles the initial binding and future
        /// binding changes to the bound ICommand
        /// </summary>
        /// <param name="inDependencyObject">A DependencyObject</param>
        /// <param name="inEventArgs">A DependencyPropertyChangedEventArgs object.</param>
        private static void PreviewDropCommandPropertyChangedCallBack(
            DependencyObject inDependencyObject, DependencyPropertyChangedEventArgs inEventArgs)
        {
            UIElement uiElement = inDependencyObject as UIElement;
            if (null == uiElement) return;

            uiElement.Drop += (sender, args) =>
            {
                GetPreviewDropCommand(uiElement).Execute(args.Data);
                args.Handled = true;
            };
        }
        #endregion
    }
}

Using the DropBehavior in XAML

Ok, now it is really easy to add this to a Grid or other UIElement that doesn’t already handle Drag and Drop. If you try to add this to a TextBlox, which already handles Drag and Drop, this even doesn’t fire.

Examples

You can add the DropBehavior to a UserControl

<UserControl x:Class="DropBehaviorExample.ExampleView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:MVVM="clr-namespace:MVVM"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="400"
             AllowDrop="True"
             MVVM:DropBehavior.PreviewDropCommand="{Binding PreviewDropCommand}"
             >
    <Grid>
    </Grid>
</UserControl>

Or you can add the DropBehavior to a Grid.

<UserControl x:Class="DropBehaviorExample.ExampleView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:MVVM="clr-namespace:MVVM"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="400"
             AllowDrop="True"
             >
    <Grid MVVM:DropBehavior.PreviewDropCommand="{Binding PreviewDropCommand}">
    </Grid>
</UserControl>

And of course, you can put it on any UIElement that doesn’t already handle drag and drop.

Implementing the ICommand in the ViewModel

The following code is all you need in your ViewModel to bind the PreviewDropCommand.

        #region The RelayCommand that implements ICommand
        public ICommand PreviewDropCommand
        {
            get { return _PreviewDropCommand ?? (_PreviewDropCommand = new RelayCommand(HandlePreviewDrop)); }
            set
            {
                _PreviewDropCommand = value;
                NotifyPropertyChanged("PreviewDropCommand");
            }
        } private ICommand _PreviewDropCommand;

        #endregion

        #region The method encapsulated in the relay command
        private void HandlePreviewDrop(object inObject)
        {
            IDataObject ido = inObject as IDataObject;
            if (null == ido) return;

            // Get all the possible format
            string[] formats = ido.GetFormats();

            // Do what you need here based on the format passed in.
            // You will probably have a few options and you need to
            // decide an order of preference.
        }
        #endregion

Hope this helps you. I know when I first rounded up this information online, it was hard to understand because of lack of preparation information, so I made sure to provide that, and also lack of comments, so I made sure to provide that.

9 Comments

  1. Scott says:

    This looked great, it just didn’t work for me. Maybe because it is old. But I can’t get past the error: “A ‘Binding’ cannot be used within a ListViewCollection.A ‘Binding’ can only be set on a DependencyProperty of a DependencyObject.” The first sentenc of the error changes to match whatever Elelment I try to put it in.

  2. Boris says:

    May be you can send me example

    XAML , Behavior , and Model

  3. doodle says:

    Hi,

    I cannot get your code to work. I am trying to drop a file from explorer onto a window (just to get it to work). nothing happens and I have copied your code pretty much verbatim.

    am a missing something? is the cursor supposed to change when you drag over the drop area?

    Thanks

  4. aabad says:

    when i use the implemented code in the xaml it says that the property DropBehavior.PreviewDropCommand does not exist in xml namespace

  5. alex says:

    dependency property should be public (line 18)
    thx, I owe you a beer 🙂

  6. Kickerskopp says:

    For your code to work with a TextBox control ( f.e. dropping a file path from the windows explorer to a TextBox I had to add the following lines:

                uiElement.PreviewDragOver += (sender, args) =&gt;
                {
                    args.Handled = true;
                };
    
                uiElement.PreviewDragEnter += (sender, args) =&gt;
                {
                    var dataObject = args.Data as DataObject;
                
                    // Check for file list
                    if (dataObject.ContainsFileDropList())
                        args.Effects = DragDropEffects.Copy;
                    else
                        args.Effects = DragDropEffects.None;
                    args.Handled = true;
                };
    

    Other than that good job and saved me some hours of work

    Thanks

  7. d-deal says:

    +1. well done!

Leave a Reply to aabad