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.
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:
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_map == null)
_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)
{
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 Pushpin
s 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
{
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
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseLeftButtonDown +=
new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
}
{
base.OnDetaching();
AssociatedObject.MouseLeftButtonDown -=
new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
DetachEvents();
}
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;
}
}
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;
}
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_map == null)
_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)
{
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; ">
}
}
public static T FindParent<T>(DependencyObject child) where T : FrameworkElement
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
T parent = parentObject as T;
if (parent == null)
return FindParent<T>(parentObject);
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.