Udostępnij za pośrednictwem


Draggable Pushpins in Bing Maps (.NET)

When building a Bing Maps application, you may want to give the user the ability to drag a pushpin. In the JavaScript version of Bing Maps this can be done by setting the draggable property of a pushpin to true, but the Pushpin class in the .NET version does not have this property, so what can you do?

In this blog post, I’ll show you how to create a reusable user control that gives you draggable pushpins, plus a lot more flexibility in terms of customization.

Some common uses for draggable pushpins include:

  • Allowing the user to edit locations on the map by moving pushpins.
  • Using draggable pushpins as handles for data points in drawing tools.

Creating the user control

To get started, open Visual Studio and create a new project in C# or Visual Basic. Select the Blank App (XAML) template, name the application and then press OK.

AddNewItem-DraggablePushpins

Next, add a reference to the Bing Maps SDK. Right click on the References folder and press Add Reference. Select Windows → Extensions, and then select Bing Maps for C#, C++ and Visual Basic. If you do not see this option, be sure to verify that you have installed the Bing Maps SDK for Windows Store apps. While you are here, also add a reference to the Microsoft Visual C++ Runtime Package, as this is required by the Bing Maps SDK when developing using C# or Visual Basic. Press OK.

ReferenceManager-DraggablePushpins

In Solution Explorer, set the Active solution platform in Visual Studio by right clicking on the Solution folder and selecting Properties. Select Configuration Properties → Configuration. Find your project and under the Platform column, set the target platform to x86, and press OK.

ConfigurationManager-DraggablePushpins

  Right click on the solution folder and select Add → New Item. Select the User Control template and give it a name, such as “DraggablePin.” Open the DraggablePin.xaml file and update the XAML to the following:

 <UserControl 
   x:Class="DraggablePushpin.DraggablePin" 
   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:local="using:DraggablePushpin" 
   xmlns:d="https://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"> 
 < Ellipse Height="30" Width="30"Fill="Blue"  
             Stroke ="White"  StrokeThickness="5" Margin="-15,-15,0,0"/>  
</UserControl> 

Now open DraggablePin.xaml.cs or DraggablePin.xaml.vb.

The code-behind for this user control will need a reference to the map control in its constructor and a local reference to it stored in a private variable. This control will use a number of events on the map to enable the dragging functionality.

Next, override the OnPointerPressed event handler to add the required map events for dragging the user control. When the user control is initially pressed, the coordinate of the center of the map will be stored. Next, fill in the ViewChange event of the map to set the center of the map to the stored center value, which will keep the map from panning when dragging the user control.

Set the PointerMovedOverride event of the map to update the position of the user control as it is dragged. Then set the OnPointerReleasedOverride event of the map to release all the events from the map that were used for dragging the user control.

Now add a Boolean property called Draggable, which can be used to disable the dragging functionality of the pushpin. Lastly, expose three Action events: DragStart, Drag, and DragEnd, for use in future applications.

The following example shows the complete code for the user control.

C#

 using  Bing.Maps;
using  System;
using  Windows.UI.Xaml.Controls;
using  Windows.UI.Xaml.Input;
namespace  DraggablePushpin
   {
   public sealed partial class DraggablePin : UserControl 
   {
   private Map  _map;
   private bool  isDragging = false;
   Location  _center;

        public  DraggablePin(Map map)
   {
   this.InitializeComponent();

            _map = map;
   }

public sealed partial class DraggablePin : UserControl { this.InitializeComponent();

     _map = map;
   }

   /// <summary> 
   /// A  boolean indicating whether the pushpin can be dragged. 
   /// </summary> 
   public bool  Draggable { get; set; }

   /// <summary> 
   ///  Occurs when the pushpin is being dragged. 
   /// </summary> 
   public Action<Location>  Drag;

   /// <summary> 
   ///  Occurs when the pushpin starts being dragged. 
   /// </summary> 
   public Action<Location>  DragStart;

   /// <summary> 
   ///  Occurs when the pushpin stops being dragged. 
   /// </summary> 
   public Action<Location>  DragEnd;

   protected override void  OnPointerPressed(PointerRoutedEventArgs e)
   {
       base.OnPointerPressed(e);

       if  (Draggable)
       {
          if  (_map != null)
         {
              //Store the  center of the map 
              _center = _map.Center;

             //Attach events  to the map to track touch and movement events 
             _map.ViewChanged +=  Map_ViewChanged;
             _map.PointerReleasedOverride += Map_PointerReleased;
             _map.PointerMovedOverride  += Map_PointerMoved;
         }

                var  pointerPosition = e.GetCurrentPoint(_map);

                Location  location = null;

                //Convert the  point pixel to a Location coordinate 
               if  (_map.TryPixelToLocation(pointerPosition.Position, out  location))

                                            {

                    MapLayer.SetPosition(this,  location);
               }

                if  (DragStart != null)
               {
                     DragStart(location);
              }

              //Enable Dragging 
              this.isDragging  = true;
           }
        }

        private void  Map_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
          //Check if the  user is currently dragging the Pushpin 
          if (this.isDragging)
        {
             //If so, move the  Pushpin to where the pointer is. 
             var  pointerPosition = e.GetCurrentPoint(_map);

           Location  location = null;

             //Convert the  point pixel to a Location coordinate 
             if  (_map.TryPixelToLocation(pointerPosition.Position, out  location))
             {
                  MapLayer.SetPosition(this,  location);
             }

             if  (Drag != null)
             {
               Drag(location);
             }
         }
     }

     private void  Map_PointerReleased(object sender, PointerRoutedEventArgs e)
    {
          //Pushpin  released, remove dragging events 
          if  (_map != null)
          {
               _map.ViewChanged -=  Map_ViewChanged;
               _map.PointerReleasedOverride -=  Map_PointerReleased;
               _map.PointerMovedOverride -=  Map_PointerMoved;
          }

          var  pointerPosition = e.GetCurrentPoint(_map);

          Location  location = null;

          //Convert the  point pixel to a Location coordinate 
          if  (_map.TryPixelToLocation(pointerPosition.Position, out  location))
          {
               MapLayer.SetPosition(this,  location);
          }

          if  (DragEnd != null)
          {
               DragEnd(location);
          }

            this.isDragging  = false;
       }

       private void  Map_ViewChanged(object sender, ViewChangedEventArgs e)
       {

       if  (isDragging)
       {
           //Reset the map  center to the stored center value. 
           //This prevents  the map from panning when we drag across it. 
           _map.Center = _center;
       }
     }
   }
}  

Visual Basic

 Imports  Bing.Maps
Public NotInheritable Class DraggablePin 
   Inherits UserControl 

   Private _map As Map 
   Private isDragging As Boolean = False 
   Private _center As Location 

   Public Sub New(map As Map)
   Me.InitializeComponent()

        _map = map
   End Sub 

   '''<summary> 
   ''' A boolean indicating whether  the pushpin can be dragged. 
   '''</summary> 
Public Property  Draggable() As Boolean 
   Get 
       Return  m_Draggable
   End Get 
   Set(value  As Boolean)
       m_Draggable = Value
   End Set 
   End Property 
   Private m_Draggable As Boolean 

   ''' <summary> 
   ''' Occurs when the pushpin is  being dragged. 
   ''' </summary> 
Public Drag As Action(Of Location)

   ''' <summary> 
   ''' Occurs when the pushpin starts  being dragged. 
   ''' </summary> 
Public DragStart As Action(Of Location)

   ''' <summary> 
   ''' Occurs when the pushpin stops  being dragged. 
   ''' </summary> 
Public DragEnd As Action(Of Location)

Protected Overrides Sub  OnPointerPressed(e As PointerRoutedEventArgs)
   MyBase.OnPointerPressed(e)

   If  Draggable Then 
       If _map  IsNot Nothing Then 
          ''Store the  center of the map 
          _center = _map.Center

          ''Attach events  to the map to track touch and movement events 
          AddHandler  _map.ViewChanged, AddressOf Map_ViewChanged
          AddHandler  _map.PointerReleasedOverride, AddressOf  Map_PointerReleased
          AddHandler  _map.PointerMovedOverride, AddressOf Map_PointerMoved
       End If 

       Dim  pointerPosition = e.GetCurrentPoint(_map)

       Dim  location As Location = Nothing 

       ''Convert the  point pixel to a Location coordinate 
       If  _map.TryPixelToLocation(pointerPosition.Position, location) Then 
           MapLayer.SetPosition(Me,  location)
       End If 

       If  DragStart IsNot Nothing Then 
           DragStart(location)
       End If 

       ''Enable Dragging 
       Me.isDragging  = True 
   End If 

End Sub 

Private Sub  Map_PointerMoved(sender As Object, e As PointerRoutedEventArgs)
   ''Check if the user  is currently dragging the Pushpin 
   If Me.isDragging  Then 
        ''If so, move the  Pushpin to where the pointer is. 
        Dim  pointerPosition = e.GetCurrentPoint(_map)

        Dim  location As Location = Nothing 

        ''Convert the  point pixel to a Location coordinate 
        If  _map.TryPixelToLocation(pointerPosition.Position, location) Then 
            MapLayer.SetPosition(Me,  location)
        End If 

        If Drag  IsNot Nothing Then 
           Drag(location)
       End If 
     End If 
 End Sub 

 Private Sub  Map_PointerReleased(sender As Object, e As PointerRoutedEventArgs)
   ''Pushpin  released, remove dragging events 
   If _map  IsNot Nothing Then 
      RemoveHandler  _map.ViewChanged, AddressOf Map_ViewChanged
      RemoveHandler  _map.PointerReleasedOverride, AddressOf  Map_PointerReleased
      RemoveHandler  _map.PointerMovedOverride, AddressOf Map_PointerMoved
   End If 

        Dim  pointerPosition = e.GetCurrentPoint(_map)

        Dim  location As Location = Nothing 

        ''Convert the  point pixel to a Location coordinate 
If  _map.TryPixelToLocation(pointerPosition.Position, location) Then 
   MapLayer.SetPosition(Me,  location)
End If 

   If  DragEnd IsNot Nothing Then 
       DragEnd(location)
   End If 

   Me.isDragging  = False 
End Sub 

Private Sub  Map_ViewChanged(sender As Object, e As ViewChangedEventArgs)
   If  isDragging Then 
       ''Reset the map  center to the stored center value. 
       ''This prevents  the map from panning when we drag across it. 
   _map.Center = _center
  End If 
 End Sub 

End Class 

Implementing the user control in an application

Now that you have a DraggablePin user control, you can now try using it in an app. Open MainPage.xaml and update the XAML to the following:

 <Page 
x:Class="DraggablePushpin.MainPage" 
   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:local="using:DraggablePushpin" 
   xmlns:d="https://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:m="using:Bing.Maps">  
    <Grid  Background="{StaticResource  ApplicationPageBackgroundThemeBrush}"> 
         <m:Map Name="MyMap" Credentials="YOUR_BING_MAPS_KEY"/>  
 < Border Background="Black" HorizontalAlignment="Center" VerticalAlignment="Bottom">  
 < TextBlock Name="CoordinatesTbx" FontSize="24" Margin="10"/>  
 </ Border >  
</Grid> 
</Page>  

Open MainPage.xaml.cs or MainPage.xaml.vb.

In the constructor, add a handler to the MapLoaded event of the map. Set the handler to create a DraggablePin and add it to the center of the map. Then attach to the Drag event of the pin and have it update the coordinates displayed in a TextBlock at the bottom of the map, as shown in the following example.

C#

 using  Windows.UI.Xaml.Controls;
namespace  DraggablePushpin
{
 public sealed partial class MainPage : Page 
 {
   public  MainPage()
   {
   this.InitializeComponent();

   MyMap.Loaded += MyMap_Loaded;
 }

 private void  MyMap_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
 {
   DraggablePin pin  = new DraggablePin(MyMap);

   //Set the  location of the pin to the center of the map. 
   Bing.Maps.MapLayer.SetPosition(pin,  MyMap.Center);

   //Set the pin as  draggable. 
   pin.Draggable = true;

   //Attach to the  drag action of the pin. 
   pin.Drag += Pin_Dragged;

   //Add the pin to the map. 
   MyMap.Children.Add(pin);
 }

 private void  Pin_Dragged(Bing.Maps.Location location)
 {
   CoordinatesTbx.Text = string.Format("{0:N5},{1:N5}",  location.Latitude, 
location.Longitude);
   }
  }
}  

Visual Basic

 Imports  Windows.UI.Xaml.Controls
Public NotInheritable Class MainPage 
  Inherits Page 
  Public Sub New()
       InitializeComponent()
    AddHandler  MyMap.Loaded, AddressOf MyMap_Loaded
  End Sub 

  Private Sub  MyMap_Loaded(sender As Object, e As  Windows.UI.Xaml.RoutedEventArgs)
     Dim pin As New DraggablePin(MyMap)

    ''Set the  location of the pin to the center of the map. 
     Bing.Maps.MapLayer.SetPosition(pin,  MyMap.Center)

    ''Set the pin as  draggable. 
     pin.Draggable = True 

    ''Attach to the  drag action of the pin. 
     pin.Drag = AddressOf  Pin_Dragged

    ''Add the pin to  the map. 
     MyMap.Children.Add(pin)
  End Sub 

  Private Sub  Pin_Dragged(location As Bing.Maps.Location)
   CoordinatesTbx.Text = String.Format("{0:N5},{1:N5}",  location.Latitude, 
location.Longitude)
   End Sub 
End Class  

Now if you run the application you will see a blue circle pushpin in the center of the map. Simply press down on it and drag it around on the screen using a mouse or your finger if you have a touch screen device.

DraggablePushpins

You can find the complete source code for this blog post in the MSDN Code Sample Gallery. If you are working with the WPF version of the control, I have a similar code sample available here.

 

Comments