Friday, October 23, 2009

Silverlight Toolkit Drag Drop (Part 2): Customizing Drag and Drop Behavior

In part 1 you were introduced to the new Silverlight DragDropTarget controls.  In addition to being powerful these controls are very flexible, allowing developers to customize almost every single aspect of their behavior.  In order to understand how to customize these controls you must first understand how they communicate.  The good news is that DDT’s communicate via a WPF-compatible implementation of the drag/drop events!

A WPF-Compatible (mostly) Implementation of Drag and Drop

The Silverlight Toolkit Drag and Drop Framework is a compatible subset of WPF’s drag and drop methods and events – with a few small exceptions.  The diagram below demonstrates how DDTs are layered on top of this WPF-compat framework.  DDTs use these events and methods to communicate with each other.

dragdropdiagram 

The fact that the Silverlight Toolkit contains a full implementation of WPF’s drag and drop stack gives the developer tremendous flexibility.  They can…

  • Use DDTs.
  • Communicate with and customize DDTs by handling the drag and drop events.
  • Choose not to use DDTs and use the WPF-compat API’s to initiate and manage their own drag operations.
  • Add native drag and drop support to their custom controls by implementing the IAcceptDrop interface.
  • Create new DDTs for existing controls by creating a class that inherits from DragDropTarget.

Differences between Silverlight Toolkit and WPF Drag and Drop API’s

Different Namespaces

The most obvious difference between the Silverlight implementation of drag and drop and that of WPF is that the Silverlight drag and drop objects are located in the Microsoft.Windows namespace rather than in the System.Windows namespace.  When writing drag and drop code it’s good practice to insert the following code in your source file:

#if SILVERLIGHT
using Microsoft.Windows;
using SW = Microsoft.Windows;
#else
using SW = System.Windows;
#endif

Using an alias for the namespace with the drag/drop objects ensures that you can write code that can be easily ported to WPF or a future version of Silverlight with core-level drag/drop support.  All of the code samples in this series of blog posts assume that you’ve defined the alias “SW” as Microsoft.Windows in Silverlight and System.Windows in WPF.

Starting a Drag Operation

Keep in mind that if you intend to use a DDT you don’t need to know how to do this because DDT’s automatically initiate drag operations when an item is dragged.  However if you want to initiate a drag operation yourself you’ll need to know about some subtle (but necessary) differences between the Silverlight’s and WPF’s DoDragDrop methods. 

In WPF the DoDragDrop method blocks until the drag operation completes.  In Silverlight there are no blocking UI methods so it is necessary to listen for a DragDropCompleted event if you want to perform an operation when a drag completes. 

It is still possible to write cross-platform code if you use the following pattern:

// Define an action to be executed when the drag completes.
Action<SW.DragDropEffects> dragDropCompleted =
    effects => 
    {
        Console.WriteLine(string.Format(“Drag finished: {0}”, effects)));
    };
    
#if SILVERLIGHT

// Create a handler for the DragDropCompleted event which invokes the action.
EventHandler<DragDropCompletedEventArgs> dragDropCompletedHandler = null;
dragDropCompletedHandler =
    (sender, args) => 
    {
        // Detach the handler so that this action is only executed once.
        SW.DragDrop.DragDropCompleted -= dragDropCompletedHandler;
        dragDropCompleted(args.Effects);
    };

// Attach the handler to the DragDropCompleted event.
SW.DragDrop.DragDropCompleted += dragDropCompletedHandler;

// Begin the drag operation.
SW.DragDrop.DoDragDrop(
    sourceControl, 
    data, 
    SW.DragDropEffects.All, 
    SW.DragDropKeyStates.LeftMouseButton);    

#else

// If WPF then just begin the drag operation.
SW.DragDropEffects effects = 
    SW.DragDrop.DoDragDrop(
        sourceControl, 
        data, 
        SW.DragDropEffects.All);
        
// Code blocks until drag is finished...
dragDropCompleted(effects);

#endif

Note that in addition to being asynchronous the Silverlight version of DoDragDrop accepts an additional parameter: the initial DragDropKeyStates.  In the example below this is SW.DragDropKeyStates.LeftMouseButton.

SW.DragDrop.DoDragDrop(
    sourceControl, 
    data, 
    SW.DragDropEffects.All, 
    SW.DragDropKeyStates.LeftMouseButton);

In Silverlight (unlike WPF) it is not possible to sample the mouse state.  Therefore the developer must track the state of the mouse keys and pass it to DoDragDrop.  This is straightforward if, as is commonly the case, the drag operation begins with a mouse click. 

button.MouseLeftButtonDown += 
    (sender, args) =>
    {
        SW.DragDrop.DoDragDrop(
            button, 
            "Some text to transfer", 
            SW.DragDropEffects.All,
            SW.DragDropKeyStates.LeftMouseButton);
    };

Although the DragDropKeyStates enumeration is a flags enum which includes members that track keyboard states there is no need to include these.  The DoDragDrop method will sample the keyboard and update the value when invoked.

Listening to Drag Source and Drag Target Events

In WPF there are two ways to attach a handler to an event.  The first is to attach a handler to the event on the UIElement and the second is to attach a handler to the attached event object:

// Works in WPF.
myButton.DragOver += 
    new SW.DragEventHandler((o, a) => Console.Write(“button dragged over.”));

// Works in WPF and Silverlight Toolkit Drag Drop Framework.
myButton.AttachEvent(
    SW.DragDrop.DragOverEvent, 
    new SW.DragEventHandler((o, a) => Console.Write(“button dragged over.”),
    false);

Silverlight Toolkit’s Drag and Drop Framework supports the latter approach for all drag target and source events.  By using AttachEvent and RemoveEvent you can write code that will work on WPF and Silverlight.  For more information on attached events see the Attached Events Overview on MSDN.

Note: DDTs implement the familiar WPF events so you don’t need to use the more verbose attached event approach when you’re working with them.

// DragDropTarget's have the WPF events.
myListBoxDragDropTarget.DragOver += 
    new SW.DragEventHandler(
        (o, a) => Console.Write(“DDTs make everything easier!”));

Indicating that an Element Can Accept a Drop Operation

In WPF all UIElements have an AllowDrop property which determines whether they will receive drag/drop events.  As it isn’t possible to add properties to existing types the Silverlight Tookit implementation of drag and drop uses an AllowDrop attached property on the static DragDrop object:

WPF

<Rectangle AllowDrop="True" />

Silverlight

<Rectangle 
    xmlns:msWindows="clr-namespace:Microsoft.Windows;assembly=System.Windows.Controls.Toolkit" 
    mswindows:DragDrop.AllowDrop="True" />

Customizing DragDropTarget Behavior

DDT’s do their best to determine the allowed effects of a drag operation by examining the bound collection and the items inside of them.  For example if a DDT’s control is bound to a ReadOnlyCollection or an array then the allowed effects of a drag will not include DragDropEffects.Move and the allowed effects of a drop will be DragDropEffects.None.  By default a DDT never sets the allowed effects of a drag to DragDropEffects.Copy because there is no generic way of cloning an item in Silverlight (ICloneable does not exist).  But what if we wanted the effect of a drop to be a Copy operation?

Let’s say we have two DDT’s, one containing a ListBox and the other containing a DataGrid.

listboxanddatagrid

<StackPanel Orientation="Horizontal">
    <controlsToolkit:ListBoxDragDropTarget x:Name="listBoxDragDropTarget"  HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
        <ListBox x:Name="listBox" VerticalAlignment="Stretch" SelectionMode="Extended"  Height="300" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding Image}" />
                        <TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="4,0,0,0" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>
    </controlsToolkit:ListBoxDragDropTarget>
    <dataToolkit:DataGridDragDropTarget x:Name="dataGridDragDropTarget" msWindows:DragDrop.AllowDrop="true"  HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
        <data:DataGrid x:Name="dataGrid" AutoGenerateColumns="False"  Height="300" >
            <data:DataGrid.Columns>
                <data:DataGridTextColumn Binding="{Binding Name}" Header="Name" />
                <data:DataGridTextColumn Binding="{Binding Reputation}" Header="Reputation"  />
                <data:DataGridTextColumn Binding="{Binding GamerScore}" Header="GamerScore" />
            </data:DataGrid.Columns>
        </data:DataGrid>
    </dataToolkit:DataGridDragDropTarget>
</StackPanel>

Each control is bound to an collection of Gamer instances.

class Gamer {
    public string Name {get; set}
    public double Reputation {get; set}
    public int GamerScore {get;set;}
    
    // Our custom copy method.
    public Gamer Clone()
    {
        return 
            new Gamer 
            {
                Name = this.Name,
                Reputation = this.Reputation,
                this.GamerScore = this.GamerScore
            };
    }
}

// snip...

// No need to use an ObservableCollection.  We don't
// want items removed from the ListBox.
listBox.ItemsSource = 
    new []
    {
        new Gamer 
        { 
           Name = "Mark Rideout",
           Reputation = 82.234,
           GamerScore = 23432
        },
        // etc.
   };

// Using an observable collection ensures that changes
// to the items source collection will be reflected 
// in the grid immediately.
dataGrid.ItemsSource =
    new ObservableCollection<Person>(); 

When the user drags Gamers from the ListBox to the DataGrid we’d like to add a copy of the Gamer object to the DataGrid rather than add a reference to the object in the ListBox to the DataGrid’s bound collection (the default action).  In order to ensure that when an item is dragged from a ListBox the allowed effect is DragDropEffects.Copy rather than DragDropEffects.Link we set the ListBoxDragDropTarget’s AllowedSourceEffects property.

listBoxDragDropTarget.AllowedSourceEffects = SW.DragDropEffects.Copy;

Since the DataGridDragDropTarget doesn’t know how to handle a Copy operation we need to handle its Drop event.

using System.Collections.ObjectModel;

// snip...
dataGridDragDropTarget.Drop += 
    (sender, args) =>
    {
        // Only handle this event if it's a copy.  If the event is not handled
        // the DDTs base implementation of Drop will execute.
        if ((args.AllowedEffects & SW.DragDropEffects.Copy) == SW.DragDropEffects.Copy)
        {
            IList<Person> dataSource = dataGrid.ItemsSource as IList<Person>;
            
            // Retrieve the dropped data in the first available format.
            object data = args.Data.GetData(args.Data.GetFormats()[0]);
            
            // The data is the ItemDragEventArgs that was created by the DDT when
            // the drag started.  It contains a SelectionCollection.
            // SelectionCollection's are used by DDTs because they can transfer 
            // multiple objects.  The fact that they store the indexes of the 
            // objects within the source collection also makes reordering items
            // within a source possible.
            ItemDragEventArgs dragEventArgs = data as ItemDragEventArgs;
            SelectionCollection selectionCollection = dragEventArgs.Data as SelectionCollection;
            if (selectionCollection != null)
            {
                IEnumerable<Gamer> gamers = selectionCollection.Select(selection => selection.Item).OfType<Gamer>();
                if (gamers.Any())
                {
                    foreach(Gamer gamer in gamers)
                    {
                        // Invoke our custom Clone method to make a copy.
                        dataSource.Add(gamer.Clone());
                    }
                    args.Effects = SW.DragDropEffects.Copy;
                    args.Handled = true;                
                }
            }
        }
    };

Now when we drag items from our ListBox to our DataGrid we will get copies of the Gamer objects and the ListBox’s contents will be unchanged.

There are many more extensibility points in DDT’s.  I encourage you to take a close look at the events and properties.  I think you’ll be impressed by how flexible these controls are.

Powerful and Flexible

By now it should be clear that the Silverlight Toolkit Drag and Drop Framework is more than a few DDTs that add drag and drop behavior to a certain Silverlight controls.  It is also a rich set of WPF-compatible events and methods that can be used with any Silverlight control - including your own!

Next time: Adding Drag and Drop support to your custom controls!

44 comments:

Will D said...

Jafar, can you post an example of an ImageDragDropTarget? I would like to be able to position items from a ListBox in various locations on an image.

Jafar Husain said...

It sounds like what you really want here is a DDT for a Canvas. Ideally this canvas would place elements wherever they were dropped. You could place your image inside this DDT.

The good news is that I'm actually working on a PanelDragDropTarget for the next blog post. I will extend this at some point and create a CanvasDragDropTarget.

Unfortunately a CanvasDragDropTarget cannot be perfect because the drop event provides no information about the mouse offset from the top-left of the item being dragged. As a result the item will jump slightly when its dropped and its top left will be at the mouse position.

I'm actually going to make a change so this information is preserved for the next release (which will be very soon).

Martijn said...

Nice Blogs (Part 1 + Part 2)! Really helped me a lot, can't wait for Part 3

212fire said...

Hi Jafar,

Great articles. I'm using the Silverlight Toolkit's listbox drag/drop target controls for an app I'm working on.

I have a question though - I have one listbox populated by Blend's sample data (temporarily)... so when I drop this listboxitem in another droptarget, I'd like to check the selected item's data properties to see if it's equal to true. If true, then I want another control to turn a different color. I just don't know how to access the dropped item's data properties so I can add this logic...

I might not be making much sense...so if you could email me back that would be great... There doesn't seem to be much documentation online on this yet.

Jafar Husain said...

212fire: If you need to look at the dropped object's properties then listen for the Drop event on your target DDT. There's an example of this in the blog post. Inside the drop event you can examine the objects in the SelectionCollection. The key trick here is to make sure NOT to set the Handled property of the event args to true. That way the default implementation of DDT will take over and cause the items to be added, BUT you'll get an opportunity to put some logic in beforehand (like changing color).

Enrique said...

Thanks for sharing so much information Jafar. Any idea when you'll post details about your PanelDragDropTarget ? I'm VERY eager to try it :)

Jafar Husain said...

I'm going to release it with the next Silverlight Toolkit release - either in the Toolkit or on my blog. The reason I'm waiting is that I've made some important changes in the API to make the PanelDDT work as smoothly as possible. Don't worry, the next release will be out _very_ soon.

escobar5 said...

Hello Jafar, very nice posts. I'm trying to do the same with the TreeViewDragDropTarget, the problem is that i don't know where the drop was done, finally i can get the treeviewitem where the drop operation was made by using VisualTreeHelper.FindElementsInHostCoordinates and the DragEventArgs.GetPosition, but i also need to check if the drop operation was made above, over or below the treeviewitem. Can you give me some advise on this.

Thanks in advance

Escobar

Colin said...

Hi Jafar,

Can drag drop be used on a listbox which is bound to a CollectionViewSource?

I've tried this but am getting weird results, such as items being copied rather than moved.

Colin said...

"Any idea when you'll post details about your PanelDragDropTarget ? I'm VERY eager to try it :)"

Me too!

I'm guessing it didn't make it into the Nov drop of the toolkit, so will it turn up on your blog soon?

Jafar Husain said...

I'll post the PanelDragDropTarget next week when I get back from my vacation. Sorry, but I promised myself I wouldn't code in my hotel room in San Francisco :-).

(answering blog comments is cool though)

Gordon Watts said...

Thanks for this and getting it in the toolkit. I'd appreciate some more info, or places to look. I have a text box that I want to be my drag source, and a list box as the destination. The list box data is not just text strings - but the data is create based on what I'm dragging...

Basically, I need to know where to read a tutorial on implementing basic D&D here. I've seen other postings around, but it isn't clear to me if I should be trying to fit into this framework you've got in the toolkit or rolling my own from scratch.

Miguel Madero said...

Jafar,

I'm trying to do a custom drap & drop. I need more control of the data that I'm sending on the args.
The SelectionCollection doesn't make a lot of sense in our case since we want to deal directly with the modifications to the objects in the Model/ViewModel level specially in cases where the drop operation will result in a service call.

I tried to customize what is set on the data of the DragEventArgs to send my own type, however if I do this it breaks with a NullReferenceException when trying to use the selection in line 352 of the DragDropTarget.

The real problem is in SelectionCollection in line 27.

Selection selection = data as Selection;
if (selection != null)
{
selection = new Selection(data);
}

The code should create a new Selection if the selection is null instead of doing it when the data was converted to a selection (which would cause other problems because we'll have a Selection with an Item of type selection).

I already fix this and seems to be working fine. It's an easy change and I could send a patch if you need me to.

BTW, another approach is to deal with this at the drop target, however I really don't want to have a bunch of if/else on the drop to accept all the different types.

I think that the right way to handle this instead of adding our object to the Data property in the ItemDragEventArgs, we should just add one or more DataFormats to the DataObject or provide our own implementation of IDataObject and then the drop target would deal with whatever format suites it better. Right now the DataObject is created internally (or I might be missing some extension points)

What do you think?

Miguel Madero said...

I also notice than when we have nested dragdroppanel the even is bubbling to the outer one. Is this a bug or by design?

Anonymous said...

Is it possible to give feedback which varies based on the index where the drop object whould be dropped within the drop target? For example, if it not possible to drop an item as the first item in the list, but it is possible to drop it as he second item in the list?

Jafar Husain said...

Miguel: It would appear that you've found a bug in SelectionCollection and I'll try and get that fixed for the next toolkit release.

You can create a new DataObject if you like. My recollection is that if the object passed to DoDragDrop is of type IDataObject then that object will be used. Otherwise a new data object will be created and the data will be nested inside. If that's not the case then that's a bug (this is WPF's behavior) and I will rectify it.

The bubbling behavior is correct and is WPF-compat.

Anonymous: It seems like determining whether an item could be dropped at a given index is the targets decision. Therefore it could set the drag drop effects based on the target index. You may have to inherit from DDT to get access to that information but it should be possible.

Miguel Madero said...

Jafar,

>IDataObject
I tried using it, but apparently I was setting it in the wrong place or something was broken.
I was setting the Data property in the ItemDragEventArgs during the ItemDragStarting event.
However, this property has the actual data and not the DataObject so I couldn't add a new format. If I added my own IDataObject a new DataObject wrapping my DataObject.
I've this working now, but let me know if you need some help to reproduce it, I could prepare a minisample that shows this behaviour.

>Bubbling
I'm sorry. I just re-read what I said and I think I was just missing the e.Handled = true to avoid the event to bubble

wahwah said...

Hello jafar,

is your PanelDragDropTarget already available? I haven't seen it in the Toolkit. Would be great, if it's coming soon!!!

Carl said...

Hello!

I needed to be able to fire an event when something is dropped on an image (in my case a recycle bin). I read through the article and found that you used the method AttachEvent. I'm using Silverlight 3 but the controls does not contain a definition for AttachEvent. I think the right method to use is AddHandler which worked for me.

Bhupesh said...

I am trying to drag an image from a list to another element inside a canvas, however I am not getting the OnDrop event. Here is the sample XAML for drop target:

<UserControl x:Class="DragDropSample.DropControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="200">
<Canvas x:Name="ParentCanvas" Width="200" Height="300" Background="AliceBlue" AllowDrop="false">
<Canvas x:Name="childCanvas" Canvas.Left="50" Canvas.Top="70" Width="80" Height="150" Background="Aqua" AllowDrop="true"/>
<Image x:Name="childImage" Source="Images/BlueLoveCirclesWallpaper.jpg" Width="200" Height="100" AllowDrop="true"/>
</Canvas>
</UserControl>

The user control’s class is inherited from IAcceptDrop.
Here are my observations (Note – I have referred elements with their x:Name attribute):
1) I am getting DragEnter event however immediately I get DragLeave event for both “childCanvas” and “childImage” which is wrong as mouse is still inside the canvas/image. As a result of this I am not getting the OnDrop event
2) If I remove the ParentCanvas and host the Image element directly inside the User control then everything works fine.
3) If I change AllowDrop for ParentCanvas to true I get the OnDrop event however the OriginalSource is the ParentCanvas and not the child element on which the drop happened.
My guess is this is a bug, any suggestions?
I want to replace the image with the drag source image.

Bhupesh said...

Got the exact issue. Toolkit is using RenderSize property to determine the original source for the drag operation (refer - Controls.Toolkit\DragDrop\DragOperation.cs, function - GetDragOverOriginalSource)

node => new Rect(new Point(0, 0), node.RenderSize).Contains(args.GetPosition(node)))

In my case the RenderSize for Image element is (0,0) and hence the original source changes to its parent. This is a bug in Silverlight refer the link: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=520507

The workaround that I can think of is to use ActualWidth/ActualHeight properties as below:
node => new Rect(new Point(0, 0), new Size( (node as FrameworkElement).ActualWidth, (node as FrameworkElement).ActualHeight)).Contains(args.GetPosition(node)))

What other approach do you suggest?

Jafar Husain said...

Good catch Bupesh,

I'll apply this workaround in the next Toolkit release.

Martin said...

Could please tell me why the method select in selectionCollection.Select statement the method doesn't exist. I am using the System.Collections.ObjectModel namespace for the SelectionCollection object.

Jafar Husain said...

The Select method is an extension method. Just import "System.Linq" and it will be added to the SelectionCollection object for you.

Colin said...

Hi Jafar,

Is there a way to disable dragging above/below nodes in the TreeViewDragDropTarget?
I can't find the exact problem, but it seems to be duplicating items (overwriting existing data!) so I need to find a way to disable it, while still enabling dragging into a node.
I can't find much info about the DragDropTargets on the web about this except for your blog!

Jafar Husain said...

Colin: Hmmm. Not really. I'd consider adding an option for the next release but it's not really a common scenario. If you disagree log a request on CodePlex and other people can vote on it.

That said it's probably a good idea to instead get to the bottom of the data duplication bug. If you think it's a TVDDT issue please create a contained repro and log it on CodePlex.

Colin said...

Hi Jafar,

I've logged the bug here:
http://silverlight.codeplex.com/WorkItem/View.aspx?WorkItemId=5213

You can see the problem here:
http://www.screencast.com/users/NovelBase/folders/Jing/media/a11eecb8-f8df-4cb9-b884-a772b0a2f6a5

Maybe I'm not understanding how the treeview & dragdrop should deal with adjusting hierarchical data correctly?

212fire said...

Hi Jafar,
I have a question... I am trying to use drag and drop in my application that allows users to "drop occupants into seats of a vehicle."
So, I have created a ListBoxDragDropTarget/ListBox with each seat having their own LBDDT/ListBox all within the vehicle's listbox data template. Then I also have a LBDDT/ListBox for the occupants the user can drag from and into the Vehicle Seat...
All of these listboxes are bound to their own ObservableCollections. The problem is that when I try to drag a person from one listbox into the Vehicle seat - it uses the DragDrop "Copy" effect rather than using the "Move" effect which is what I need... Please let me know what you think or how I can fix this...

Thanks Jafar!

Jafar Husain said...

212fire: Are you sure you're bound to an OC. This might happen if you were bound to a ReadOnlyCollection or an array, or you are overriding the default action to copy.

Are you holding down control when you drag? That switches the default action to copy.

212fire said...

Hi Jafar,

Ok, nevermind about my previous requests. What I ended up doing was removing the ItemSource bindings and created a few targetedtriggeractions to work around the problem. Basically, the drag and drop works now properly although I could not bind the ItemSource..(I am manually populating the listbox since there will always only be one item).

I do have just one more question... I noticed that if you are trying to drag and drop into a listbox that there is this insertion line that appears on DragEnter... In my case, I don't want to show it... Is it possible to remove the insertion line or hide it ?

Thank you,
Jenna

Jafar Husain said...

Hey Jenna,

Sure you can hide it. Just retemplate the control. You'll see the insertion indicator path in there. Just remove it or set it's opacity to 0.

Sergey Prokhorenko said...

Hi. I use DataGridDragDropTarget which works fine, but whatever row I drag, it always shows the first source grid's row as the transparent dragged image. Is it a bug? Also, how can I setup my own dragged image? Is it via DragDecoratorContent in ItemDragStarting?

Another question, how can I detect the target row in the target grid when dropped? Currently I do this using mouse position and hit testing, because I haven't found any "target row" data in events.

B said...

Hi Jafar,
1) I noticed that when we are dragging the mouse faster then the dropping is not happening. This is happening in the SLToolkit sample as well. The item being dragged gets removed from source list, however it does not appear in the destination list box.
2) Another problem is related to positioning of the tranparent background (decorator). When you start drag for the first time just notice that it appears at 0,0 and not at the mouse location. Once you drop it, then subsequent drag-drop operation starts the background image at the last drop position. Dragging slowly hides the problem somewhat, however it is more prominent when you drag faster.

The reason for this seems to be the below code in DragDropTarget.cs:

private void OnItemDragStarted(ItemDragEventArgs args)
{
_currentItemDragEventArgs = args;
_itemWasDroppedOnSource = false;

GeneralTransform transform = Application.Current.RootVisual.SafeTransformToVisual(_dragPopup);
Point offset = new Point(0, 0);
if (transform != null)
{
offset = transform.Transform(new Point(0, 0));
}

_dragPopup.HorizontalOffset = offset.X;
_dragPopup.VerticalOffset = offset.Y;
...
}


as the actual location is being decided in:
private void OnDragging(MouseEventArgs args)
{
Point mouseLocation = args.GetSafePosition(Application.Current.RootVisual);
if (double.IsNaN(mouseLocation.X))
{
mouseLocation = new Point(0, 0);
}
Canvas.SetLeft(_dragDecorator, mouseLocation.X - _currentItemDragEventArgs.DragDecoratorContentMouseOffset.X);
Canvas.SetTop(_dragDecorator, mouseLocation.Y - _currentItemDragEventArgs.DragDecoratorContentMouseOffset.Y);
}


And I think, when dragging mouse faster OnDragging is not getting called.

Should I log a bug for both of these issues?

Anonymous said...

Hi I need to drag / drop data grid item into stack panel.

Cant figure it out.

I also cant find AttachEvent anywhere (tried object browser in VS, text search in Toolkit Nov 09 source / samples)...

Where in Silverlight is AttachEvent ???


Thank you

Jafar Husain said...

Sergey Prokhorenko: This embarrassing bug has been fixed in the new version of the Toolkit which should be released very soon.

B: Please do log those bugs in CodePlex and include all of that information. Thanks very much.

Anonymous: AttachEvent is a wrong. It should be AddHandler. Sorry for the confusion.

Shivaji said...

How do I customize it to allow the drag and drop only between the elements at same heirarchy level in a tree view. Currently I see that the elements are draggable across heirarchies. Basically what I need is, interchanging the elements' position which are in the same heirarchy level in a treeview.

Scott B said...

I'm having problems inheriting a DDT. I simply created a class that inherits from the TreeViewDragDropTarget. I can create a UserControl, put the new DDT inside of it with a TreeView inside of the new DDT. So far so good... The problem is that I can not place that UserControl on any page or other UserControl without Blend giving me the big red box error:
ArgumentNullException: Value cannot be null.
Parameter name: stream

However, the code compiles and runs.

I have no idea why I can't do a simple inheritance. Any ideas? I must be missing something.

Thanks for your help.

michael_p_lockwood said...

Jafar,

I have used the DragDropTargets with a standard dataGrid, but now i am attempting this with the datagrid's itemsource being bound to a RIA DomainDataSource. For some reason this does not allow for drag or drop, have you seen this?

Anonymous said...

Hi Jafar!

How do you drag a data grid header content and copy/drop it to an itemscontrol?

Avinash said...

hi Jafar,
I want to know how to
retemplate the ListBoxDragDropTarget control, and how change the color of insertion indicator and hide the drag snapshot .please help me

Jafar Husain said...

Shivaji:

If you want to limit tree view item moves to within the current level of hierarchy then you have to hook the DragOver,DragEnter,DragLeave, and Drop events and set the Effects property of the DragEventArgs to None when the target location is outside of the current hierarchy.

I suggest you subclass TVDDT. This will give you access to the protected GetDropTarget which accepts a DragEventArgs. This method will allow you to find out the intended drop target of a drag operation.

Scott B:

The Blend exception is almost certainly due to the fact that DDT inherits from ContentControl and when Blend inserts a CC onto a design surface it adds some content to it. This wouldn't be a problem but the DDT's throw an exception unless their Content is set to the correct type. A ListBoxDDT throws if its content is not set to a ListBox, etc.

The solution is to create a DefaultInitializer for your custom DDT. By doing so you can prevent Blend from adding Content to the DDT at design-time. See my former manager Ning Zhang's blog for details. http://www.ningzhang.org/2009/05/11/editing-model-default-initializer-for-silverlight-controls/

michael_p_lockwood:

In order to drag and drop from a data source it must implement various interfaces (INCC and IList). I suspect that the DomainDataSource dosn't implement one of these.

Avinash:

In order to hide the drag image you can hook the ItemDragStarting event and set the DragDecoratorContent property on the ItemDragEventArgs to null. If you want to change the style of the drag decorators there is a template part called "InsertionIndicator" of type path.

Stephen said...

For copying from a listbox to a treeview, I use an ItemDragStarting on the source ListBoxDragDropTarget..

private void ListBoxDragDropTarget_ItemDragStarting(object sender, ItemDragEventArgs e)
{
((SelectionCollection)e.Data)[0] = new System.Collections.ObjectModel.Selection(((InteractiveNode)((SelectionCollection)e.Data)[0].Item).Clone());
}

Only implemented for single item, (InteractiveNode is my dragged object)

Steve Ronald

Anonymous said...

Hi Jafar,

I have a ListBox and a Treeview.I can drag items from ListBox and drop on to Treeview. I don't want to drop item above or below the treeview node. I tried to do in the drop event of TreeViewDragDropTarget but of no use. I am not able to cancel the drop event. How can I achieve this? Please help me.

Anonymous said...

Jafar, I wrapped the Drag Drop API you created in the Silverlight Toolkit into DragDrop using attached properties. I got the idea from Pavan Podila's work in WPF that really helped me earlier. Here is the link: http://sl4dragdrop.codeplex.com/ . Hopefully it will be of some help to other people.

About Me

My Photo
I'm a software developer who started programming at age 16 and never saw any reason to stop. I'm working on the Presentation Platform Controls team at Microsoft. My primary interests are functional programming, and Rich Internet Applications.