Drag and Drop in MVVM with ScatterView
Asked Answered
D

2

6

I'm trying to implement drag and drop functionality in a Surface Application that is built using the MVVM pattern. I'm struggling to come up with a means to implement this while adhering to the MVVM pattern. Though I'm trying to do this within a Surface Application I think the solution is general enough to apply to WPF as well.

I'm trying to produce the following functionality:

  • User contacts a FrameworkElement within a ScatterViewItem to begin a drag operation (a specific part of the ScatterViewItem initiates the drag/drop functionality)
  • When the drag operation begins a copy of that ScatterViewItem is created and imposed upon the original ScatterViewItem, the copy is what the user will drag and ultimately drop
  • The user can drop the item onto another ScatterViewItem (placed in a separate ScatterView)

The overall interaction is quite similar to the ShoppingCart application provided in the Surface SDK, except that the source objects are contained within a ScatterView rather than a ListBox.

I'm unsure how to proceeded in order to enable the proper communication between my ViewModels in order to provide this functionality. The main issue I've encountered is replicating the ScatterViewItem when the user contacts the FrameworkElement.

Dorsum answered 23/6, 2009 at 18:42 Comment(2)
Is there any chance of seeing some code? How do ScatterViewItems hold children controls? How are you binding this to a viewmodel?Curtin
I'll try to provide a 'real' answer later, but basically a drag&drop operation happens primarily in the views. The fact that a drag is happening probably doesn't require any communication with your original view's viewmodel until the drop happens. When the drop is detected, you would either call a method or execute a command in your viewmodel and pass in the information about what was dropped. The VM would then add it to a list that is bound to your destination scatterview. The origin view should also process the drop completed call and pass that on to its viewmodel.Algie
A
4

You could use an attached property. Create an attached property and in the setproperty method bind to the droped event :


public static void SetDropCommand(ListView source, ICommand command)
        {
            source.Drop += (sender, args) =>
                               {
                                   var data = args.Data.GetData("FileDrop");
                                   command.Execute(data);
                               };
        }

Then you can bind a command in your view model to the relevant control on the view. Obviously you may want to make your attached property apply to your specific control type rather than a listview.

Hope that helps.

Associationism answered 3/9, 2009 at 19:52 Comment(0)
B
2

I had a go at getting Steve Psaltis's idea working. It took a while - custom dependency properties are easy to get wrong. It looks to me like the SetXXX is the wrong place to put your side-effects - WPF doesn't have to go though there, it can go directly to DependencyObject.SetValue, but the PropertyChangedCallback will always be called.

So, here's complete and working the code for the custom attached property:

using System.Windows;
using System.Windows.Input;

namespace WpfApplication1
{
    public static class PropertyHelper
    {
        public static readonly DependencyProperty DropCommandProperty = DependencyProperty.RegisterAttached(
            "DropCommand",
            typeof(ICommand),
            typeof(PropertyHelper),
            new PropertyMetadata(null, OnDropCommandChange));

        public static void SetDropCommand(DependencyObject source, ICommand value)
        {
            source.SetValue(DropCommandProperty, value);
        }

        public static ICommand GetDropCommand(DependencyObject source)
        {
            return (ICommand)source.GetValue(DropCommandProperty);
        }

        private static void OnDropCommandChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ICommand command = e.NewValue as ICommand;
            UIElement uiElement = d as UIElement;
            if (command != null && uiElement != null)
            {
                uiElement.Drop += (sender, args) => command.Execute(args.Data);
            }

            // todo: if e.OldValue is not null, detatch the handler that references it
        }
    }
}

In the XAML markup where you want to use this, you can do e.g.

xmlns:local="clr-namespace:WpfApplication1"
...
<Button Content="Drop here" Padding="12" AllowDrop="True"
   local:PropertyHelper.DropCommand="{Binding DropCommand}" />

.. And the rest is just making sure that your ViewModel, bindings and command is right.

This version passes an IDataObject through to the command which seems better to me - you can query it for files or whatever in the command. But that's just a current preference, not an essential feature of the answer.

Bucky answered 23/6, 2009 at 18:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.