Home > behavior, Bing maps, Silverlight > Draggable PushPins

Draggable PushPins

I must apologise that it has taken me so long to post a blog entry. I’ve been busy playing about with code contracts and working with Bing Maps in Silverlight – there’s been a lot for me to take in. Actually, the Bing maps part is the reason for this posting, and I hope that it really helps you out.

A common requirement in GIS applications is to take a point of interest from one location and move it to another by dragging it. This is behaviour that you would expect to be standard, but surprisingly enough it is not present with a default Pushpin in Silverlight Bing maps. In this post, we’ll discuss adding a Blend Behavior that provides this drag and drop functionality.

As usual, when writing a behavior, you start off by inheriting from the generic Behavior class, telling it which framework element you want to associate the behavior with.

public class DraggablePushpin : Behavior<Pushpin>
{
}

Now we want to tell it what happens when we attach the object. This is done in the OnAttached override.

/// <summary>
/// Hook the event handlers to this instance.
/// </summary>
protected override void OnAttached(){
base.OnAttached();
 AssociatedObject.MouseLeftButtonDown +=
new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
}

Basically, when the left mouse button is pressed on the Pushpin, we’re going to invoke an event handler that is going to do the grunt work for handling the relevant events that we need to work with. The definition of this method looks like this:

/// <summary>
/// Called when the left mouse button is pressed on a
/// Point of Interest.
/// </summary>
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_map == null)
// Find the map this pushpin is attached to.
_map = FindParent<Map>(AssociatedObject);
  if (_map != null)
{
if (this.ParentMapMousePanHandler == null)
{
this.ParentMapMousePanHandler = new EventHandler<MapMouseDragEventArgs>
((s, epan) => epan.Handled = this._isdragging);
_map.MousePan += ParentMapMousePanHandler;
}
if (this.ParentMapMouseLeftButtonEventHandler == null)
{
this.ParentMapMouseLeftButtonEventHandler = new MouseButtonEventHandler
((s, args) => ConfirmMoveLocation());
_map.MouseLeftButtonUp += this.ParentMapMouseLeftButtonEventHandler;
}
if (this.ParentMapMouseMoveHandler == null)
{
// If the mouse is performing a drag operation, convert the
// underlying location based on the position of the mouse
// on the viewport to a map based location.
this.ParentMapMouseMoveHandler = new MouseEventHandler(
(s, mouseargs) =>
{
if (_isdragging)
_location = _map.ViewportPointToLocation(mouseargs.GetPosition(_map));
});
_map.MouseMove += this.ParentMapMouseMoveHandler;
}
}
_isdragging = true;
}

This method starts off by identifying the map that owns the pushpin. This means traversing the visual tree until the map is identified (I’ve done this because a common way of allocating Pushpins to a map is to host them in a DataTemplate which causes problems when attempting to find the parent map just by methods on the Pushpin). Once the map has been identified, we are going to handle the following map events:

Mouse pan
Mouse left button up
Mouse move.

When the mouse pans, the event handler uses a flag to determine whether or not a Pushpin is being dragged and sets the Handled parameter as necessary.

When the mouse moves, the event handler converts the position of the mouse on the map into a location coordinate.

When the left button is released, the behavior calls a method to prompt the user whether or not they wish to move the location and updates the position accordingly.
Finally, we have methods available to detach the events. The whole behaviour is shown here:

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using Microsoft.Maps.MapControl;
using Microsoft.Maps.MapControl.Core;
namespace Pointilist.Behaviors{
/// <summary>
/// This behaviour adds drag capabilities to a Bing Maps <see cref="Pushpin"/> control.
/// </summary>
public class DraggablePushpin : Behavior<Pushpin>
{
#region Members
private bool _isdragging = false;
private MapBase _map = null;
private EventHandler<MapMouseDragEventArgs> ParentMapMousePanHandler;
private MouseButtonEventHandler ParentMapMouseLeftButtonEventHandler;
private MouseEventHandler ParentMapMouseMoveHandler;
private Location _location;
#endregion
    /// <summary>
/// Hook the event handlers to this instance.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
      AssociatedObject.MouseLeftButtonDown +=
new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
    }
    /// <summary>
/// Unhook the events when this is detaching.
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
      AssociatedObject.MouseLeftButtonDown -=
new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
      DetachEvents();
}

    /// <summary>
/// Make sure that no mouse events are left dangling.
/// </summary>
private void DetachEvents()
{
if (_map == null) return;
if (this.ParentMapMousePanHandler != null)
{
_map.MousePan -= ParentMapMousePanHandler;
ParentMapMousePanHandler = null;
}
if (ParentMapMouseLeftButtonEventHandler != null)
{
_map.MouseLeftButtonUp -= ParentMapMouseLeftButtonEventHandler;
ParentMapMouseLeftButtonEventHandler = null;
}
if (ParentMapMouseMoveHandler != null)
{
_map.MouseMove -= ParentMapMouseMoveHandler;
ParentMapMouseMoveHandler = null;
}
}
    /// <summary>
/// Only move the poi if the user accepts the change
/// (at which point, it's saved to the database).
/// </summary>
private void ConfirmMoveLocation()
{
_isdragging = false;
if (_location == null) return;

      DetachEvents();
      MessageBoxResult result = MessageBox.Show("Are you sure you want to move here?",
"Move location?", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
AssociatedObject.Location = _location;
}
      _location = null;
}
    /// <summary>
/// Called when the left mouse button is pressed on a
/// Point of Interest.
/// </summary>
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_map == null)
// Find the map this pushpin is attached to.
_map = FindParent<Map>(AssociatedObject);
      if (_map != null)
{
if (this.ParentMapMousePanHandler == null)
{
this.ParentMapMousePanHandler = new EventHandler<MapMouseDragEventArgs>
((s, epan) => epan.Handled = this._isdragging);
_map.MousePan += ParentMapMousePanHandler;
}
if (this.ParentMapMouseLeftButtonEventHandler == null)
{
this.ParentMapMouseLeftButtonEventHandler = new MouseButtonEventHandler
((s, args) => ConfirmMoveLocation());
_map.MouseLeftButtonUp += this.ParentMapMouseLeftButtonEventHandler;
}
if (this.ParentMapMouseMoveHandler == null)
{
// If the mouse is performing a drag operation, convert the
// underlying location based on the position of the mouse
// on the viewport to a map based location.
this.ParentMapMouseMoveHandler = new MouseEventHandler(
(s, mouseargs) =>
{
if (_isdragging)
_location = _map.ViewportPointToLocation(mouseargs.GetPosition(_map));
});
_map.MouseMove += this.ParentMapMouseMoveHandler;
}
}
_isdragging = true;
}
    /// <summary>
/// Find the relevant parent item for a particular dependency object.
/// </summary>
/// <typeparam name="T">The type of object to search for.</typeparam>
/// <param name="child">The <see cref="DependencyObject"/> to start searching from.</param>
/// <returns>The parent object if found, null otherwise.</returns>
public static T FindParent<T>(DependencyObject child) where T : FrameworkElement
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
      // If parentObject is null, we've reached the top of the tree without finding the item we were looking for.
if (parentObject == null) return null;
      T parent = parentObject as T;
// If parent is null, recursively call this method.
if (parent == null)
return FindParent<T>(parentObject);
      // If we reach this point, we have found the parent item we are looking for.
return parent;
}
}
}

Now, it’s a simple matter to hook it up to a Pushpin.

<m:Pushpin m:MapLayer.Position="{Binding Location, Mode=TwoWay}"
Visibility="{Binding IsVisible, Converter={StaticResource VisibilityConverter}}"
PositionOrigin="Center" ToolTipService.ToolTip="{Binding Address}">
<interact:Interaction.Behaviors>
<behavior:DraggablePushpin />
</interact:Interaction.Behaviors>
</m:Pushpin>

There you have it – a draggable Pushpin. I hope that this helps you as much as it’s helping me.

About these ads
  1. Brian Cook
    April 11, 2012 at 12:48 pm

    I like what I see. This is one item I have been looking for. Would have a sample project with this class includeded in it?

    Thanks,

  2. Michael CooperMike Cooper
    December 29, 2012 at 1:00 pm

    Nice. The only negative is that we don’t see a “ghost” while dragging … no user feedback doesn’t help. Any ideas?

  1. January 2, 2011 at 12:16 am
  2. September 27, 2012 at 2:11 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Canny Coder

Java 8 Functional Programming with Lambda Expressions

pihole.org

Adventures in theoretical computer science, with your host, chaiguy1337

Confessions of a coder

Confessions of a WPF lover

WordPress.com

WordPress.com is the best place for your personal blog or business site.

Follow

Get every new post delivered to your Inbox.

Join 38 other followers

%d bloggers like this: