Wednesday 10 July 2013

Windows 8 MVVM Drag Drop

Overview

I’ve recently added drag/drop functionality to a Windows 8 app between a GridView and a ListBox using an MVVM pattern. I created two Attached Properties to collect the dragged items and fire a command when they were dropped. Both work on ListViewBase controls.

DragItemStarting Attached Property


This detects the DragItemsStarting event on the source control and pushes the items into a bound IList<T> in the view model:

public class DragItemsStarting
{
    public static readonly DependencyProperty ItemsProperty =
            DependencyProperty.RegisterAttached("Items", typeof(IList<object>), typeof(DragItemsStarting), new PropertyMetadata(null, ItemsPropertyChanged));

    public static void SetItems(DependencyObject attached, IList<object> value)
    {
        attached.SetValue(ItemsProperty, value);
    }

    public static IList<object> GetItems(DependencyObject attached)
    {
        return (IList<object>)attached.GetValue(ItemsProperty);
    }

    private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Attach click handler
        (d as ListViewBase).DragItemsStarting += DragItemstarting_DragItemsStarting;
    }
             
    static void DragItemstarting_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
    {
        // Set Items
        SetItems(sender as DependencyObject, e.Items);
    }
}

DragItem Binding XAML


<GridView
    x:Name="pallette"
    cmd:DragItemsStarting.Items="{Binding DragObjects, Mode=TwoWay}"

DragItem Binding View Model

public IList<object> DragObjects
{
    get { return this._dragObjects; }
    set { this._dragObjects= value; }
}

Be careful to implement the getter and not return null as the attached property won’t attach if the value doesn’t change.

DropCommand Attached Property

This detects the drop event and fires the bound command. In my implementation, I wanted the pointer location so I passed that into the command:

public class DropCommand
{
    public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(DropCommand), new PropertyMetadata(null, CommandPropertyChanged));

    public static void SetCommand(DependencyObject attached, ICommand value)
    {
        attached.SetValue(CommandProperty, value);
    }

    public static ICommand GetCommand(DependencyObject attached)
    {
        return (ICommand)attached.GetValue(CommandProperty);
    }

    private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Attach click handler
        (d as FrameworkElement).Drop += DropCommand_Drop;
    }

    private static void DropCommand_Drop(object sender, DragEventArgs e)
    {
        e.Handled = true;

        // Get element
        var fe = (sender as FrameworkElement);

        // Get command
        ICommand command = GetCommand(fe);

        // Get pointer point
        var p = Window.Current.CoreWindow.PointerPosition;

        Debug.WriteLine(string.Format("DROP X: {0}, Y: {1}", p.X, p.Y));

        // Execute command
        command.Execute(p);
    }
}

DropCommand Binding XAML


<ListBox cmd:DropCommand.Command="{Binding DropCommand}"

DropCommand Binding View Model


public ICommand DropCommand
{
    get { return this._dropCommand; }
}

I usually have a method called by the constructor that created the commands:

this._dropCommand = new DelegateCommand(a =>
    {
        if (a is Windows.Foundation.Point)
        {
            this.DoSomething((Windows.Foundation.Point)a);
        }
       
    }, p => this.IsDropExecutable);

You can use any type of command; I've used Delegate command taken from the Prism framework. 

Conclusion

This provides a nice clean way of implementing drag and drop using an MVVM pattern.


2 comments:

  1. Dear Geoff,

    really good article ,besides the colors :)

    but I am not clear with this sentences

    static void DragItemstarting_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
    {

    this property DragItemsStartingEventArgs is not exist in the context ,and ,could you please publish you base of listview;

    ReplyDelete
  2. Are you using Win8 or WP8 ListViewBase is the base type for GridView and ListBox

    ReplyDelete