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.