Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Draggable PushPins

, 10 Oct 2010
Rate this:
Please Sign up or sign in to vote.
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.

/// <span class="code-SummaryComment"><summary>
</span>/// Hook the event handlers to this instance.
/// <span class="code-SummaryComment"></summary>
</span>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:

/// <span class="code-SummaryComment"><summary>
</span>/// Called when the left mouse button is pressed on a
/// Point of Interest.
/// <span class="code-SummaryComment"></summary>
</span>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
{
  /// <span class="code-SummaryComment"><summary>
</span>  /// This behaviour adds drag capabilities to a Bing Maps <span class="code-SummaryComment"><see cref="Pushpin"/> control.
</span>  /// <span class="code-SummaryComment"></summary>
</span>  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

    /// <span class="code-SummaryComment"><summary>
</span>    /// Hook the event handlers to this instance.
    /// <span class="code-SummaryComment"></summary>
</span>    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.MouseLeftButtonDown +=
        new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
    }
    /// <span class="code-SummaryComment"><summary>
</span>    /// Unhook the events when this is detaching.
    /// <span class="code-SummaryComment"></summary>protected override void OnDetaching()
</span>    {
      base.OnDetaching();
      AssociatedObject.MouseLeftButtonDown -=
        new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
      DetachEvents();
    }
    /// <span class="code-SummaryComment"><summary>
</span>    /// Make sure that no mouse events are left dangling.
    /// <span class="code-SummaryComment"></summary>
</span>    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;
      }
    }
    /// <span class="code-SummaryComment"><summary>
</span>    /// Only move the poi if the user accepts the change
    /// (at which point, it's saved to the database).
    /// <span class="code-SummaryComment"></summary>
</span>    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;  
    }
    /// <span class="code-SummaryComment"><summary>
</span>    /// Called when the left mouse button is pressed on a
    /// Point of Interest.
    /// <span class="code-SummaryComment"></summary>
</span>    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; style="font-size: 13px; 
		line-height: 16px; white-space: normal; ">

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.


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Pete O'Hanlon
CEO
United Kingdom United Kingdom
A developer for over 30 years, I've been lucky enough to write articles and applications for Code Project as well as the Intel Ultimate Coder - Going Perceptual challenge. I live in the North East of England with 2 wonderful daughters and a wonderful wife.
 
I am not the Stig, but I do wish I had Lotus Tuned Suspension.
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralMy vote of 5 PinmemberRichard Waddell11-Oct-10 15:03 
GeneralRe: My vote of 5 PinmvpPete O'Hanlon12-Oct-10 3:53 
GeneralMy vote of 5 Pinmemberlinuxjr11-Oct-10 11:44 
GeneralRe: My vote of 5 PinmvpPete O'Hanlon12-Oct-10 3:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140922.1 | Last Updated 10 Oct 2010
Article Copyright 2010 by Pete O'Hanlon
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid