Drag & Drop binging events to ViewModel

Oct 13, 2013 at 10:31 PM
Last night I was looking into drag&drop functionality because I needed to drag items from the list to another control. If I get it correctly , now the controller pass drag event to TreeItemEx and from there the only option is to bind it to a function in the item itself. It's not possible to bind the event to the ViewModel (or to the codebehind) since a reference to TreeItemEx/item is not passed to the method.

This is very inconvenient.
One reason is that we don't always have source access to the items we want to add to the list. Even if we do, those objects might also be used in other scenarios as well. I don't feel comfortable placing UI login into the Model. That's what the ViewModel is for.

Also, most of the time those items would be just lightweight items that don't know much about their context. They most certainly default to a simple {return this;} inside OnDrag().
Implementing CanDrag() is also complicated. The viewModel must constantly update the AllowDrag state on all items, or each object must keep a reference to the ViewModel and query it about the current state of the application.

It would be much more useful for the TreeViewEx Control to dispatch drag events/Commands to the ViewModel. That would allow a wider range of scenarios and be inline with the MVVM paradigm.
Coordinator
Oct 14, 2013 at 8:03 AM
So the idea is to have another mechanism that targets some common view model?

I could imagine to implement all 7 delegates (CanInsert, Drag, ...) on TreeViewEx, with a reference to the node. So the dragNdrop controller would check the nodes first and the treeview as second. This is because I think each node to provide its own status, is the most common use case.
Oct 15, 2013 at 12:45 PM
That would be great.

Since the delegate would have access to the node reference it could be easy for the developer to just query the Node for it's status.
Of course this would add some more code so I am ok with providing two options.

There are a lot of senarios as well where you can't store the CanDrag/etc on the node.
Imagine for example a client that retrieves a list of items from a web service. VS will automatically generate the Classes that represent those data.
You 'd like to bind those data into TreeViewEx and then drag one item into another control to initiate a process.

Another example, imagine a layout editor for something like XAML, where you want to visualize the content in a treeview. You either don't have the source or don't want to pollute the layout elements with the Editor's UI logic.
Oct 15, 2013 at 2:41 PM
In case someone want to detach the item, It would be nice if the StartDrag & DoDrag pass the parent TreeViewExItem as the sender of the event.
...or the parent item.

Traversing the tree for this reason wont work, not only its slow but there is no guaranty that an item isn't bind twice at different places.
Oct 16, 2013 at 11:06 AM
I was also trying to clean up my code and I wanted to propose a solution that may satisfy everyone.
I would prefer to use Command over event because it's more MVVM compilant, and they already have the CanExecute/Execute mecanism.
So, we could have:
  • in TreeViewExItem, three bindable commands : DragCommand, InsertCommand, DropCommand. We can use Tuple<int, object> as parameter for the InsertCommand. It is not perfect but at least its in mscorlib so no need for additional dependencies.
  • CanInsert and CanInsertFormat should be replaced by the CanExecute method. Is there a problem to have a single "check if I can" method? - same for CanDrop/CanDropFormat. We can again use Tuple to concatenate arguments into a single object.
  • TreeViewEx would also have these tree commands, with an additional parameter being the ViewModel of the targeted node. This allows to bind commands that are outside of the view model, to deport the work when we dont have access to the viewmodel source.
  • So the drag'n'drop controller would try to invoke commands in the items first, but if they are null, it would fallback on the commands of the TreeViewEx.
If this solution satisfies you, I can implement it very quicky.
Oct 16, 2013 at 11:16 PM
Personally I prefer events, they are strongly typed, but I guess Commands are the new standard when working with MVVM so I try to enforce them when I can so I don't get outdated. I think you get better support on VS/blend editors with commands.

Now to your points:

1) are you referring to DragContent dependencies?
Once we get Drag/Insert/Drop on TreeViewEx/ViewModel level we don't have to worry about putting System.Windows.Controls.DragNDrop dependencies to our Model projects (in case your model is separate from your WPF/UI project). DragContent carries some useful info for the operation and can get extended in the future. In case you want to drop items into multiple controls, IDataObject support multiple formats, so you can contstract a DragContent as well to enable dropping into TreeViewEx.
So, I don't see much of a problem there.
As I said above, it would be great if DragContent provide the additional info of the parent item in the hirierchy, so one could easily detach/move items around. The samples so far only demonstrate Copy.

2) Maybe slompf can enlighten us what's the typical use of CanDropFormat and if a CanDrop with access to DragContent can replace it.

3) I really don't get this one. You bind commands to your ViewModel. You always have access to ViewModel source, its part of your project. It decouples your View from the Model and contains all the business logic.
How can the targeted node have a different ViewModel? Perhaps its a different treevViewEx or the target is some other control, ok, so why do we need to pass a reference to it's own ViewModel? Perhaps you were talking about something else I I misanterstant, can you clarify?


The final word of how things get done comes to slompf, he done a terrific job so far and has a better understanding of the overall design and where it's going.
For example, I see a problem of how a DragCommand would return the desired object since commands return void. (in the samples onDrag returns 'this' but perhaps one would like to return 'this.Name' or something else...). The command parameter could be a struct where you supposed to cast (that's why I prefer events) and fill it up, or initiate the Drag yourself (not very good, a lot could go wrong).
Oct 17, 2013 at 11:01 AM
Yes, the problem of events is especially that they are strongly typed (and not easily bindable).

1) You're not supposed to reference types from your view (ie. TreeViewEx) in the ViewModel. For example in my project I have a single ViewModel which is shared between several views, and one of them is in an assembly that can't reference TreeViewEx. I propose to pass the members of the DragContent object as a Tuple. This is not ideal, but at least it does not require any additional dependency. I'm open to other solutions to avoid using an object that would require a reference to the TreeViewEx assembly, such as maybe a property container / key>value collection.
Referencing the parent would not be a problem, it could just be an additional item to the container used as argument to the commands. However I would recommend that each ViewModel item references its parent itself, but sometimes it's just not possible :)

3) I will explain my idea better with some code snippets, but I just want to answer your last point before (the return value of onDrag). I didn't think of that point but as you said, it could be easy to put the result in a field of the tuple/container of the command. This requires a cast, sure, but that's how many things in the MVVM design already works (bindings, dependency properties, commands...). Not that I like it, but it helps to detach the different layers of the design. As long as the struct is a container provided by mscorlib or any standard assembly, it would be good for me.

So let me just write some snippets of my idea, it could be easier to understand (it's not particularly complicated at all)
I meant to add the following dependency properties in TreeViewExItem:
DependencyProperty DragCommandProperty = DependencyProperty.Register("DragCommand", typeof(ICommand), typeof(TreeViewExItem));
DependencyProperty DropCommandProperty = DependencyProperty.Register("DropCommand", typeof(ICommand), typeof(TreeViewExItem));
DependencyProperty InsertCommandProperty = DependencyProperty.Register("InsertCommand", typeof(ICommand), typeof(TreeViewExItem));
The DragNDropController class can then invoke these commands. For example, CanDrop would do (in a simplified way):
if (item.DropCommand != null)
{
    if (item.DropCommand.CanExecute(Tuple.Create<string, object>(format, data))
        return true;
}
return false;
And OnDrop would do:
item.DropCommand.Execute(Tuple.Create<string, object>(format, data))
Then you can very easily create these commands in your ViewModel and handle them in your item style:
<TreeViewEx.ItemContainerStyle>
  <Style BasedOn="...">
    <Setter Property="DropCommand" Value="{Binding MyDropCommand}/>
  </Style>
</TreeViewEx.ItemContainerStyle>
But as you say, sometimes you just can't afford to modify the item's viewmodel. So I proposed a fallback method to handle drag'n'drop commands, directly in the TreeViewEx itself and in the treeview data context (viewmodel), which should be the container of the item view models. This is purely additional and for the convenience of the user only:
<TreeViewEx DropCommand="{Binding MyDropCommand}"/>
Then the DragNDropController could fallback this way, passing the item viewmodel to a more global command:
if (item.DropCommand != null)
    ...
else if (treeview.DropCommand != null)
{
    if (treeview.DropCommand.CanExecute(Tuple.Create<string, object, object>(format, data, item.DataContext))
        return true;
}
Oct 23, 2013 at 4:11 AM
By the way, if slompf decides to do a event-based system it's not a problem at all, since I can still make my own Behavior to achieve what I want to do. So it's really up to you :)
Coordinator
Oct 25, 2013 at 7:12 AM
Ok, so eventually I've manged to work through your ideas. ;)

I see it as common sense, that commands might be a better idea. Because:
  • They are more easy to bind. The method I use at the moment needs an additional class, which has to be referenced from the treeviewex assembly. Mostly no problem, but maybe sometimes.
  • It is the more convenient way, because everybody knows it.
  • The CanDrop and CanDropFormat function can be merged. The idea was: if the viewmodel cannot work with the data, why should I call GetData? But doing it does not call a very great overhead and it will simplify the interface.
But with the solution given by Benelitz, there are some points we should think of:
  • Tuple is a .Net 4 class. At the moment does not need very much change to compile the project to .Net 3.5. Do we really want to abondon the people beeing bound to 3.5? But we could use some kind of Array or dictionary to provide more data.
  • As found by nkast: OnDrag has an return value. That one would have to be put into an given array. Not very nice.
  • What would be a good objects to add as array content to the command: IDataObject of .Net maybe, DragContent if applicatble, format, Node on global commands. What else?
In general I'd like to focus on fixing the current release (Again many thanks for the massive testing). So everything we decide will be a task for the next release. In order to preapare, we could branch.
Oct 25, 2013 at 9:21 AM
That would be great slompf!

Meanhwile i started experimenting on passing Proxy classes instead of the actuall items. In part because I knew that this would take some time but also to address other issues i am having. So for now, I believe I can work with the current TreeViewEx.