This is Part 2 of my tutorial on Drag and Drop on Microsoft Surface.
First of all, we should go over the general flow of a drag and drop operation. Every method that is called in order is listed below. If methods loop back to a previous method that has already been listed, then it is not included. This list just shows every method used in the order it is first called.
I should note that 90% of this code is reusable, and those classes we will add to a folder called "ReusableClasses". From then on, anytime you want to implement drag and drop functionality, simply add the reusable classes folder. If you want to just copy over the reusable classes, the only steps you would need to implement after that would be 1, 2, 11, 24, 26, 32, 33, 36 (which is the order the methods are called in).
I originally got the idea for this tutorial when I was trying to implement drag and drop functionality on Surface for the first time. I basically had to reverse engineer the ShoppingCart sample that comes with the SDK, but that sample also contains a lot of code that is unnecessary for the most basic drag and drop operations. For example, the "checkout pod" and all the code required to determine if you were dragging out of or into a pod. So, I tried to distill this sample down to its simplest possible form; unfortunately, it still isn't that simple. The goal of this tutorial is to give someone a start-to-finish guide of all the steps involved, so you don't have to go through the whole reverse engineering process like I did. If anything isn't clear, accurate, or complete, please add a comment so I can improve this guide as much as possible. I plan to add more detail, explanation, and tips here as time goes on. I just wanted to get something out there to help out folks on this common, but far from trivial Surface task.
The first thing that happens is the following Routed Events are registered (in the SurfaceDragDrop class):
Then, we proceed with calling the methods (the class the method is in is listed after the method, xaml.cs is the code in your Window):
Now that we have a general understanding of the code flow of a single drag and drop operation, let's get started making it happen.
OnPreviewContactChanged
To our surfaceListBox1, we are going to want to attach this event:
PreviewContactChanged="OnPreviewContactChanged"
If you use intellisense to create the event handler, then your code behind should now contain this event handler:
private void OnPreviewContactChanged(object sender, ContactEventArgs e)
{
}
The completed method looks like this:
private void OnPreviewContactChanged(object sender, ContactEventArgs e)
{
ListBox listBox = sender as ListBox;
if(listBox != null)
{
TryStartDragDrop(listBox, e);
return;
}
}
Basically, we want to catch the ContactChanged event as it is tunneling down to the source of the contact. Then, if the sender of the event turns out to be a listbox, we call the TryStartDragDrop method. Which happens to be our next step.
- Here is the complete code for the
TryStartDragDrop method:
private void TryStartDragDrop(ListBox sourceListBox, ContactEventArgs e)
{
if(e.Contact.GetUserData(dragIgnoreKey) != null)
{
return;
}
Vector draggedDelta = ContactsHelper.DraggedDelta(e.Contact,
(UIElement)sourceListBox);
if (Math.Abs(draggedDelta.Y) > DragThreshold)
{
e.Contact.SetUserData(dragIgnoreKey, true);
return;
}
if (Math.Abs(draggedDelta.X) < DragThreshold)
{
return;
}
DependencyObject downSource = ContactsHelper.GetDragSource(e.Contact);
if(downSource == null)
{
return;
}
SurfaceListBoxItem sourceListBoxItem = GetParent<SurfaceListBoxItem>(downSource);
if(sourceListBoxItem == null || !sourceListBoxItem.IsHitTestVisible)
{
return;
}
Product data = sourceListBoxItem.Content as Product;
if(data == null)
{
return;
}
Size sourceSize = new Size(sourceListBoxItem.ActualWidth -
sourceListBoxItem.Padding.Left - sourceListBoxItem.Padding.Right,
sourceListBoxItem.ActualHeight - sourceListBoxItem.Padding.Top -
sourceListBoxItem.Padding.Bottom);
IEnumerable<Contact> draggingContacts =
Merge(sourceListBoxItem.ContactsCapturedWithin, e.Contact);
RotateTransform rotateTransform =
((FrameworkElement)sourceListBox).RenderTransform as RotateTransform;
double orientation = rotateTransform != null ? rotateTransform.Angle : 0;
BeginDragDrop(sourceListBoxItem, sourceSize, orientation, draggingContacts);
e.Handled = true;
}
We are going to need to add a couple of class level fields at the top to accommodate this method. Please add the following right after the opening curly brace of the SurfaceWindow1.xaml.cs class:
private static readonly object dragIgnoreKey = new object();
private const int DragThreshold = 15;
The first part of this method determines if the user is actually trying to do a drag and drop operation: by comparing the distance the user tried to drag an item against the set dragThreshold of 15. The down source is where the user actually made contact, which is then used by the GetParent method to determine the SurfaceListBoxItem that is the parent of this original contact. Then, we need to make sure that our sourceListBoxItem actually has content; otherwise, why drag it anywhere. The next important thing is the call to BeginDragDrop, but first, we make a call to DraggedDelta, which is our next step.
- We are now moving to another class, so please create a new class by right-clicking on the DragDropTutorial project node, then choosing "Add", then "New Class"; name it ContactsHelper.cs. The first overload of
DraggedDelta in ContactsHelper.cs that we call looks like this:
public static Vector DraggedDelta(Contact contact, UIElement relativeTo)
{
if (contact == null)
{
throw new ArgumentNullException("contact");
}
if (relativeTo == null)
{
throw new ArgumentNullException("relativeTo");
}
Window window = contact.ActiveSource.RootVisual as Window;
if (window != null)
{
return DraggedDelta(contact, window, relativeTo);
}
return ZeroVector;
}
There's nothing really that interesting here, except that it calls a different overload of DraggedDelta.
- The next overload of
DraggedDelta called looks like this:
private static Vector DraggedDelta(Contact contact,
Window window, UIElement relativeTo)
{
Point currentPosition = contact.GetPosition(window);
object downState = contact.GetUserData(key);
if (downState == null)
{
downState = new DragState(currentPosition,
(DependencyObject)contact.DirectlyOver);
contact.SetUserData(key, downState);
return ZeroVector;
}
Point downPosition = ((DragState)downState).Position;
if (relativeTo != window)
{
currentPosition = window.TranslatePoint(currentPosition, relativeTo);
downPosition = window.TranslatePoint(downPosition, relativeTo);
}
Vector delta = currentPosition - downPosition;
return delta;
}
This version of the overload is what really does our calculation to determine if we met the dragThreshold for our drag and drop yet.
- The next method called is
OnPreviewContactDown, which looks like this:
private static void OnPreviewContactDown(object sender, ContactEventArgs args)
{
Window window = (Window)sender;
DragState downState = new DragState(args.GetPosition(window),
(DependencyObject)args.Contact.DirectlyOver);
args.Contact.SetUserData(key, downState);
}
This event handler was registered in the ContactsHelper constructor, which was already called.
- The next step in our drag and drop journey is the
GetDragSource, also in the ContactsHelper class, and it looks like this:
public static DependencyObject GetDragSource(Contact contact)
{
if (contact == null)
{
throw new ArgumentNullException("contact");
}
object dragState = contact.GetUserData(key);
if (dragState == null)
{
Window window = contact.ActiveSource.RootVisual as Window;
if (window != null)
{
DependencyObject directlyOver = (DependencyObject)contact.DirectlyOver;
Point currentPosition = contact.GetPosition(window);
dragState = new DragState(currentPosition, directlyOver);
contact.SetUserData(key, dragState);
return directlyOver;
}
return null;
}
return ((DragState)dragState).Source;
}
The purpose of this method is probably pretty obvious. We need to know on what visual object the drag drop operation began on and then return it as a DependencyObject.
- Now, we need to look at the
GetParent method in our SurfaceWindow1.xaml.cs file:
private static T GetParent<T>(DependencyObject child) where T: DependencyObject
{
return (T)SurfaceDragDrop.FindParent(child, delegate(DependencyObject d)
{
return d is T;
});
}
This generic method is responsible for calling the SurfaceDragDrop FindParent method and giving it the type of parent we are looking for. In this case, it is SurfaceListBoxItem.
- We need to add a new class to hold our next method called
SurfaceDragDrop, which we will add the same way we did for the ContactsHelper class. In it, we will add the FindParent method like so:
internal static DependencyObject FindParent(DependencyObject child,
Predicate<DependencyObject> predicate)
{
return SurfaceDragDropHost.FindParent(child, predicate);
}
Obviously, all we are doing is calling another version of FindParent which is in the SurfaceDragDropHost class.
- Now, we need to add this class as well. The other
FindParent method looks like this:
internal static DependencyObject FindParent(DependencyObject child,
Predicate<DependencyObject> predicate)
{
Visual visualChild = child as Visual;
DependencyObject visualParent = null;
if (child != null)
{
visualParent = VisualTreeHelper.GetParent(visualChild);
if (visualParent != null)
{
if (predicate(visualParent) ||
(visualParent =
FindParent(visualParent, predicate)) != null)
{
return visualParent;
}
}
}
DependencyObject logicalParent = LogicalTreeHelper.GetParent(child);
if (logicalParent != null)
{
if (visualParent == logicalParent)
{
return null;
}
if (predicate(logicalParent) || (logicalParent =
FindParent(logicalParent, predicate)) != null)
{
return logicalParent;
}
}
return null;
}
Obviously, this is where the real logic to the FindParent operation lies.
- Now, it's time to start with the
BeginDragDrop method in the SurfaceDragDrop class, which looks like this:
public static SurfaceDragCursor BeginDragDrop(FrameworkElement source, object data,
FrameworkElement cursorContent, double orientation,
IEnumerable<Contact> contacts)
{
if (cursorContent == null)
{
throw new ArgumentNullException("element");
}
if (contacts == null)
{
throw new ArgumentNullException("contacts");
}
Contact firstContact = null;
foreach (Contact contact in contacts)
{
if (contact == null)
{
throw new ArgumentException("Null contact.", "contacts");
}
if (firstContact == null)
{
firstContact = contact;
}
}
if (firstContact == null)
{
throw new ArgumentException("Invalid number of contacts.",
"contacts");
}
if (hitTest == null)
{
throw new ArgumentNullException("hitTest");
}
System.Windows.Window window =
System.Windows.Window.GetWindow(firstContact.ActiveSource.RootVisual);
SurfaceDragDropHost host =
SurfaceDragDropHost.GetOrCreateInstance(window, SurfaceDragDrop.hitTest);
if (host == null)
{
return null;
}
return host.BeginDragDrop(source, data, cursorContent, orientation, contacts);
}
- Next is the
Merge method that you put into SurfaceWindow1.xaml.cs, which looks like this:
private static IEnumerable<Contact> Merge(ReadOnlyContactCollection contacts,
Contact extraContact)
{
bool duplicate = false;
foreach(Contact contact in contacts)
{
if(contact == extraContact)
{
duplicate = true;
}
yield return contact;
}
if(!duplicate)
{
yield return extraContact;
}
}
The purpose of the Merge method is it merges the given collection and the item without duplications. Then, it returns a new enumerator.
- The next method is found in
SurfaceDragDropHost called GetOrCreateInstance, which looks like this:
public static SurfaceDragDropHost GetOrCreateInstance(System.Windows.Window window,
SurfaceDragDropHitTest hitTest)
{
SurfaceDragDropHost host = GetInstance(window);
if (host != null)
{
return host;
}
UIElement windowContent = window.Content as UIElement;
if (windowContent == null)
{
return null;
}
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(windowContent);
if (adornerLayer == null)
{
return null;
}
host = new SurfaceDragDropHost(window, hitTest);
adornerLayer.Add(new ElementAdorner(windowContent, host));
window.UpdateLayout();
window.RegisterName(host.Name, host);
return host;
}
This method tries to get an instance of SurfaceDragDropHost for the given window, or creates a new one if it doesn't exist.
- Our next method again comes from
SurfaceDragDropHost called GetInstance, shown here:
public static SurfaceDragDropHost GetInstance(System.Windows.Window window)
{
SurfaceDragDropHost host = window.FindName(HostName) as SurfaceDragDropHost;
return host;
}
This method returns the existing instance of SurfaceDragDropHost if it exists.
- The next method is
GetVisualChild:
protected override Visual GetVisualChild(int index)
{
return adorningElement;
}
This method gets the visual by index.
- Next is
BeginDragDrop. This time in the SurfaceDragDropHost class:
public SurfaceDragCursor BeginDragDrop(FrameworkElement source, object data,
FrameworkElement cursorContent, double orientation,
IEnumerable<Contact> contacts)
{
ScatterViewItem visual = new ScatterViewItem();
visual.Template = this.template;
visual.Content = cursorContent;
visual.Height = cursorContent.Height;
visual.Width = cursorContent.Width;
visual.MinWidth = 1;
visual.MinHeight = 1;
visual.IsHitTestVisible = true;
visual.DecelerationRate = double.NaN;
Point contactCenter = GetCenterPosition(contacts, this);
visual.Center = new Point(contactCenter.X, contactCenter.Y);
visual.Orientation = orientation;
visual.CanScale = false;
visual.IsActive = true;
SurfaceDragCursor cursor = new SurfaceDragCursor(source, data, visual);
this.cursors.Add(visual, cursor);
Items.Add(visual);
foreach (Contact contact in contacts)
{
if (!contact.Capture(visual))
{
Items.Remove(visual);
return null;
}
}
if (!visual.IsAnyContactOver)
{
return null;
}
cursor.Target = HitTest(cursor);
return cursor;
}
This method begins a drag-and-drop operation:
- constructs a
ScatterViewItem to host the element being dragged
- adds it to the
ScatterView
- listens for
ScatterViewItem's Move and Complete events to manage the drag and drop process
- The next method is
GetCenterPosition in the SurfaceDragDropHost class:
private static Point GetCenterPosition(IEnumerable<Contact> contacts,
UIElement relativeTo)
{
Point center = new Point();
int count = 0;
foreach (Contact c in contacts)
{
Point position = c.GetPosition(relativeTo);
center += (Vector)position;
count++;
}
if (count > 0)
{
center.X /= count;
center.Y /= count;
}
return center;
}
This method finds the center point of a set of contacts relative to a specific element.
- The next method is
HitTest, also in the SurfaceDragDropHost class:
private FrameworkElement HitTest(SurfaceDragCursor cursor)
{
FrameworkElement target = this.hitTest.HitTest(this.ownerWindow,
GetBoundaryRect(cursor.Visual),
cursor.Visual.Orientation,
cursor.Visual.RenderTransformOrigin);
if (target == null)
{
return null;
}
SurfaceDragDropQueryTargetEventArgs args =
new SurfaceDragDropQueryTargetEventArgs(target, cursor);
target.RaiseEvent(args);
return args.ProposedTarget as FrameworkElement;
}
This method performs hit testing, and calls the QueryTarget routed event on the target to refine the hit test result.
- The next method is
GetBoundaryRect in the SurfaceDragDropHost class:
private static Rect GetBoundaryRect(ScatterViewItem item)
{
double itemLeft = item.Center.X - item.ActualWidth / 2;
double itemTop = item.Center.Y - item.ActualHeight / 2;
return new Rect(itemLeft, itemTop, item.ActualWidth, item.ActualHeight);
}
This method returns the boundary rectangle for a given ScatterViewItem.
- The next method is another method named
HitTest in the SurfaceDragDropHitTest class (which is new, so we need to create it like we did the others):
public override FrameworkElement HitTest(Window window,
Rect rect, double orientation, Point relativeOrigin)
{
Geometry geometry = GetGeometry(rect, orientation, relativeOrigin);
FrameworkElement hitTestResult = null;
VisualTreeHelper.HitTest(window,
delegate(DependencyObject potentialHitTestTarget)
{
FrameworkElement element = potentialHitTestTarget as FrameworkElement;
if (element != null)
{
if (element is SurfaceDragDropHost)
{
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}
if (!element.IsVisible || element.Opacity <= 0.0)
{
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}
if (element.AllowDrop)
{
hitTestResult = element;
return HitTestFilterBehavior.Stop;
}
}
return HitTestFilterBehavior.Continue;
},
delegate(HitTestResult result) { return HitTestResultBehavior.Stop; },
new GeometryHitTestParameters(geometry));
return hitTestResult;
}
This method returns the first element the given rect is over.
- The next method is
GetGeometry, still in the SurfaceDragDropHitTest class:
private static Geometry GetGeometry(Rect rect, double orientation,
Point relativeOrigin)
{
Geometry geometry = new RectangleGeometry(rect);
Matrix matrix = new Matrix();
Point origin = rect.TopLeft + new Vector(relativeOrigin.X * rect.Width,
relativeOrigin.Y * rect.Height);
matrix.Translate(-origin.X, -origin.Y);
matrix.Rotate(orientation);
matrix.Translate(origin.X, origin.Y);
geometry.Transform = new MatrixTransform(matrix);
return geometry;
}
This method constructs a RectangleGeometry instance for a given rect and orientation.
- The next method is
CursorMoved in the SurfaceDragDropHost class:
private void CursorMoved(object sender, RoutedEventArgs args)
{
args.Handled = true;
ScatterViewItem visual = (ScatterViewItem)args.Source;
SurfaceDragCursor cursor = this.cursors[visual];
FrameworkElement newTarget = HitTest(cursor);
FrameworkElement oldTarget = cursor.Target;
if (oldTarget != newTarget)
{
cursor.Target = newTarget;
if (oldTarget != null)
{
RaiseEvent(SurfaceDragDrop.SurfaceDragLeaveEvent,
oldTarget, cursor);
}
cursor.DragDropEffects = DragDropEffects.None;
if (cursor.Source != null)
{
cursor.DragDropEffects = RaiseEvent(
SurfaceDragDrop.SurfaceDragGiveFeedbackEvent,
cursor.Source, cursor);
}
if (newTarget != null)
{
cursor.DragDropEffects =
RaiseEvent(SurfaceDragDrop.SurfaceDragEnterEvent,
newTarget, cursor);
}
}
else
{
if (cursor.Source != null)
{
cursor.DragDropEffects =
RaiseEvent(SurfaceDragDrop.SurfaceDragGiveFeedbackEvent,
cursor.Source, cursor);
}
}
if (cursor.Target != null)
{
cursor.DragDropEffects =
RaiseEvent(SurfaceDragDrop.SurfaceDragOverEvent,
cursor.Target, cursor);
}
}
This method is a handler which is called when the position or orientation of the ScatterViewItem is changed.
- The next one is
RaiseEvent in SurfaceDragDropHost:
private static DragDropEffects RaiseEvent(RoutedEvent routedEvent,
FrameworkElement routedEventSource, SurfaceDragCursor cursor)
{
SurfaceDragDropEventArgs args = new SurfaceDragDropEventArgs(routedEvent,
routedEventSource, cursor);
routedEventSource.RaiseEvent(args);
return args.ProposedEffects;
}
This method raises a routed event.
- The next method is
InvokeEventHandler in the SurfaceDragDropEventArgs class (which we still need to add to our reusable classes folder):
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void InvokeEventHandler(Delegate genericHandler,
object genericTarget)
{
((EventHandler<SurfaceDragDropEventArgs>)genericHandler)(genericTarget, this);
}
This method provides correct type casting to the SurfaceDragDropEventHandler.
- The next method is
OnQueryTargetOverScatterView in SurfaceWindow1.xaml.cs:
private void OnQueryTargetOverScatterView(object sender,
SurfaceDragDropQueryTargetEventArgs e)
{
ScatterView targetScatterView = (ScatterView)sender;
Point relativePosition = e.Cursor.GetPosition(targetScatterView);
e.ProposedTarget = targetScatterView;
}
This method handles the QueryTargetOver event for the ScatterView1. The parent ScatterView is the target for all drag-drops that occur within its bounds, even ones that occur over both the ScatterView and a ScatterViewItem on that ScatterView. QueryTargetOver gives the parent ScatterView the chance to make one of its children the drop target instead of itself.
- The next method is
GetPosition in the SurfaceDragCursor class (which we now need to add to our reusable classes folder):
public Point GetPosition(UIElement relativeTo)
{
Point center = new Point(visual.Width / 2, visual.Height / 2);
return this.visual.TranslatePoint(center, relativeTo);
}
This method returns the center of the cursor relative to a given element.
- The next method is
OnDragEnterOverScatterView found in SurfaceWindow1.xaml.cs:
private void OnDragEnterOverScatterView(object sender, SurfaceDragDropEventArgs e)
{
ScatterView targetScatterView = (ScatterView)sender;
Highlight(targetScatterView, true);
e.ProposedEffects = DragDropEffects.Move;
e.Handled = true;
}
This method handles the Enter event for a ScatterView as a target.
- The next method is
GetCursorsOver in the SurfaceDragDrop class:
public static ReadOnlyCollection<SurfaceDragCursor>
GetCursorsOver(FrameworkElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
SurfaceDragDropHost host = FindHostForElement(element);
if (host == null)
{
return SurfaceDragDropHost.EmptySurfaceDraggedElementCollection;
}
return host.GetCursorsOver(element);
}
This method returns a collection of cursors over the given element.
- The next method is
FindHostForElement found in the SurfaceDragDrop class:
internal static SurfaceDragDropHost FindHostForElement(FrameworkElement element)
{
System.Windows.Window window = System.Windows.Window.GetWindow(element);
return SurfaceDragDropHost.GetInstance(window);
}
This method finds the SurfaceDragDropHost for the window that owns the element.
- The next method is
IsCursorOver found in the SurfaceDragDropHost class:
private static bool IsCursorOver(SurfaceDragCursor cursor,
FrameworkElement element)
{
return cursor.Target != null && (cursor.Target == element ||
FindParent(cursor.Target, delegate(DependencyObject d)
{return d == element;}) != null);
}
This method determines if a specific cursor is over a specific FrameworkElement.
- The next method is
CursorLostCapture also found in SurfaceDragDropHost:
private void CursorLostCapture(object sender, ContactEventArgs args)
{
args.Handled = true;
ScatterViewItem visual = args.OriginalSource as ScatterViewItem;
if (visual != null)
{
SurfaceDragCursor cursor;
if (this.cursors.TryGetValue(visual, out cursor) &&
!visual.IsAnyContactCaptured)
{
EndDragDrop(cursor);
}
}
}
- The next method is
EndDragDrop found in SurfaceDragDrop:
public static void EndDragDrop(SurfaceDragCursor cursor)
{
if (cursor == null)
{
throw new ArgumentNullException("cursor");
}
SurfaceDragDropHost host = FindHostForElement (cursor.Visual);
if (host != null)
{
host.EndDragDrop(cursor);
}
}
- The next method is
OnDropOverScatterView found in SurfaceWindow1.xaml.cs:
private void OnDropOverScatterView(object sender, SurfaceDragDropEventArgs e)
{
ScatterView targetScatterView = (ScatterView)sender;
ScatterViewItem itemToAdd = CreateScatterViewItemFromCursor(e.Cursor);
Rect boundary = new Rect(0, 0, targetScatterView.ActualWidth,
targetScatterView.ActualHeight);
if(!boundary.Contains(itemToAdd.Center))
return;
itemToAdd.IsActive = true;
targetScatterView.Items.Add(itemToAdd);
itemToAdd.IsActive = false;
Highlight(targetScatterView, false);
e.ProposedEffects = DragDropEffects.Move;
e.Handled = true;
}
This method handles the Drop event for a ScatterView as a target.
- The next method is
CreateScatterViewItemFromCursor found in SurfaceWindow1.xaml.cs:
private ScatterViewItem CreateScatterViewItemFromCursor(SurfaceDragCursor cursor)
{
ScatterViewItem svi = new ScatterViewItem();
ScatterViewItem sourceScatterViewItem = cursor.Source as ScatterViewItem;
if(sourceScatterViewItem != null)
{
object sourceContent = sourceScatterViewItem.Content;
sourceScatterViewItem.Content = null;
svi.DataContext = sourceScatterViewItem.DataContext;
svi.Content = sourceContent;
svi.Height = sourceScatterViewItem.Height;
svi.Width = sourceScatterViewItem.Width;
}
else
{
Image contentImage = new Image();
ContentControl source = (ContentControl)cursor.Source;
Product sourceData = (Product)source.Content;
string imagePath = sourceData.MediumImageUrl;
Uri imageUri2 = new Uri(imagePath);
BitmapImage bitmap = new BitmapImage(imageUri2);
contentImage.Source = bitmap;
contentImage.Stretch = Stretch.Uniform;
svi.DataContext = cursor.Source.DataContext;
svi.Content = contentImage;
svi.Height = 140;
svi.Width = 100;
}
svi.MaxHeight = 300;
svi.Center = cursor.Visual.Center;
svi.Orientation = cursor.Orientation;
svi.ScatterManipulationCompleted += new
ScatterManipulationCompletedEventHandler(ScatterManipulationCompleted);
svi.ScatterManipulationDelta += new
ScatterManipulationDeltaEventHandler(ScatterManipulationDeltaChanged);
return svi;
}
This method creates a ScatterViewItem from a SurfaceDragCursor.
- The next method
GetIsAnyCursorOver is found in SurfaceDragDrop:
public static bool GetIsAnyCursorOver(FrameworkElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
SurfaceDragDropHost host = FindHostForElement(element);
if (host == null)
{
return false;
}
return host.GetIsAnyCursorOver(element);
}
This method returns true if there is at least one cursor over the given element.
- The next method is the second
GetIsAnyCursorOver found in SurfaceDragDropHost:
public bool GetIsAnyCursorOver(FrameworkElement element)
{
foreach (SurfaceDragCursor cursor in this.cursors.Values)
{
if (IsCursorOver(cursor, element))
{
return true;
}
}
return false;
}
- This method returns true if there is at least one cursor over the given element.
- The last method is
OnDragCompleted, found in SurfaceWindow1.xaml.cs:
private void OnDragCompleted(object sender, SurfaceDragDropEventArgs args)
{
SurfaceListBox senderListBox = sender as SurfaceListBox;
if(senderListBox != null)
{
args.Cursor.Source.Opacity = 1;
args.Cursor.Source.IsHitTestVisible = true;
}
activeItemsUnderManipulation.Remove(args.Cursor);
}
This method handles the DragCompleted event for a source item.
Now, let's finish up the XAML and see things actually drag and drop. First, your surfaceListView1 should look like this after you have entered the two event handlers that are highlighted:
Now, you should be able to drag and drop away. If yours doesn't work for some reason, I am attaching the final product for you to download and compare. If I have forgotten a step in this tutorial, please let me know so I can fix it.