Creating Silverlight Microsoft Bing Maps with Editable Pushpins in .NET

modified

Introduction

A variety of software mapping development kits are available for use in C# ASP .NET web applications, including Google Maps, Mapquest, Bing, and more. However, with Microsoft’s recently released Bing Maps for Silverlight and C# ASP .NET, it’s become quite easy to develop a mapping web application with visually enhanced graphics and smooth transition effects.

In this article, we’ll step through the process of creating a basic C# ASP .NET web application, using Silverlight and the Microsoft Bing Maps SDK. We’ll create a map with an Aerial and Road view, and allow the user the create pushpin marks by clicking the map. When the user hovers the mouse over a pushpin, we’ll display a popup textbox, containing the latitude, longitude, and a custom message. The user will be able to click a pushpin to edit the text, providing the framework for persisting map location information to a database, web service, or network.

A Microsoft Silverlight Bing Map with 3 pushpins created.
A Microsoft Silverlight Bing Map with 3 pushpins created.

Checking the Tool Belt

Before beginning, you’ll need to download and install the Silverlight SDK, web application templates for Visual Studio, and the Microsoft Bing Map SDK. To implement the WCF service and data access layer, you’ll need the Entity Framework and Linquify. You can download the source code to this project here.

With the Silverlight SDK installed, you can begin creating a web application in Visual Studio by selecting File->New Project and choose Silverlight Application. Visual Studio will automatically generate a Silverlight Application project, containing a MainPage.xaml, and a Web application project, containing a Default.aspx and associated Siliverlight javascript files.

Setting Up the References

To begin using the Microsoft Bing Map SDK, we’ll need to add two references to our Silverlight project. You can do this by right-clicking the Silverlight project in Visual Studio (the project containing App.xaml and MainPage.xaml) and selecting Add Reference. Then browse to select the following references:

1
2
3
C:\Program Files (x86)\Bing Maps Silverlight Control\V1\Libraries\Microsoft.Maps.MapControl.dll
C:\Program Files (x86)\Bing Maps Silverlight Control\V1\Libraries\Microsoft.Maps.MapControl.Common.dll

Next, add the Microsoft Bing Map SDK using statement to the top of the MainPage.xaml.cs file, as follows:

1
2
using Microsoft.Maps.MapControl;

The final step in our setup is to add the namespace reference in the MainPage.xaml file to reference the Bing Map SDK. You can do this by adding the reference into the UserControl tag at the top of the MainPage.xaml, as follows:

We’ll be adding the tag:

1
2
xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

The complete UserControl tag will appear as:

1
2
3
4
5
6
7
8
<UserControl x:Class="BingMapTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">

Notice, we’ve added the xmlns:m namespace within the UserControl tag. This allows us to reference the Bing Mapping controls from the SDK within the Silverlight XAML file.

We can now get started with setting up the user interface controls.

Making a Map

Adding a Microsoft Bing Silverlight Map to a page is quite easy, and simply involves adding the tag <m:Map /> to the page. However, we’ll add a few extra parameters to set the display mode, assign a name for our server-side code to reference, and provide credential information.

Our mapping tag will appear as follows:

1
2
3
<m:Map CredentialsProvider="your-credentials-key" Name="ctlMap" Mode="AerialWithLabels"> 
</m:Map>

Notice, you’ll need a CredentialsProvider key in order to use the Bing Map SDK. You can obtain a key for free at the Bing Maps Portal. Once signed up, you’ll be able to access the server to pull mapping information.

Hovering the mouse over a pushpin displays an InfoBox popup with text.
Hovering the mouse over a pushpin displays an InfoBox popup with text.

Silverlight Microsoft Bing Map Edit Mode for the pushpin
Clicking the pushpin displays the InfoBox popup in edit mode, allowing the user to change the text associated with the pushpin. After editing the text, the user can click the ‘X’ button to close the InfoBox, save the location data, and set the map back to View mode.

Making an InfoBox

While the above XAML will produce a basic map, we need to add a few more tags to support our pushpin markers and popup textbox labels. We can do this by adding the following complete code to our MainPage.xaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<UserControl x:Class="BingMapTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">

<Grid x:Name="LayoutRoot">

<m:Map CredentialsProvider="your-credentials-key" Name="ctlMap" Mode="AerialWithLabels">

<Border MinHeight="40" MaxHeight="40" Width="55"
Background="White"
Opacity="0.9"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="5"
HorizontalAlignment="Right"
VerticalAlignment="Top">
<StackPanel Orientation="Horizontal" Opacity="0.9" Grid.Column="0" Grid.Row="1"
HorizontalAlignment="Center">
<Button Click="btnClear_Click" Tag="Clear" Margin="5" Background="Black"
HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBlock>Clear</TextBlock>
</Button>
</StackPanel>
</Border>

<m:MapLayer>
<Border x:Name="Infobox" MinHeight="100" MaxHeight="200" Width="300"
Background="Black"
Opacity="0.9"
BorderBrush="White"
BorderThickness="2"
CornerRadius="5"
Visibility="Collapsed">
<StackPanel>
<Button Click="btnClose_Click" Name="btnClose" Tag="Close" Margin="5" Background="Black"
HorizontalAlignment="Right" VerticalAlignment="Top" Visibility="Collapsed">
<TextBlock>X</TextBlock>
</Button>
<Grid>
<TextBlock x:Name="InfoboxTitle" Foreground="White" FontSize="12" Padding="5" Width="280"
TextWrapping="Wrap" Grid.Row="0" HorizontalAlignment="Left" />
</Grid>
<TextBlock x:Name="InfoboxDescription" Foreground="White" FontSize="10" Padding="5" Width="265"
TextWrapping="Wrap" Height="Auto" Grid.Row="1" />
<TextBox x:Name="InfoboxDescriptionEdit" Foreground="Black" FontSize="10" Padding="5" Width="265"
TextWrapping="Wrap" Height="Auto" Grid.Row="1" Visibility="Collapsed" />
</StackPanel>
</Border>
</m:MapLayer>

</m:Map>

</Grid>
</UserControl>

Notice in the above code, we’ve created a bordered menu, containing a Clear button. This will allow us to clear all pushpins from the map. It also provides an example of how to create a button in a Silverlight C# ASP .NET web application and handle the click event.

We’ve also defined a new map layer, containing a bordered InfoBox. This is our popup text, which will appear when the user hovers the mouse over a pushpin. The InfoBox contains a TextBlock, which serves as our read-only label for the pushpin. It also contains a TextBox, which serves as the edit field when the user clicks the pushpin to edit its text. We’ll control the visiblity of the InfoBox in the server-side C# code so that the TextBlock is only visible when in hover-mode and the TextBox is only visible when in edit-mode.

With our XAML code complete, we can move on to adding functionality for the map.

Initializing the Map

We’ll start by initialing our map to a specific location, based upon a latitude and longitude. While you can easily lookup a desired location, for this example we’ll choose (40.13, -74.46), which happens to be Trenton, NJ.

1
2
3
4
5
6
7
8
9
10
11
12
public MainPage()
{
InitializeComponent();

// Initialize the map.
ctlMap.Center = new Location(40.13, -74.46);
ctlMap.ZoomLevel = 8;

// Add a click event handler for adding new pushpins.
ctlMap.MouseClick += new EventHandler<MapMouseEventArgs>(ctlMap_MouseClick);
}

In the above code, we’ve assigned a location to the Center property of the map. This tells the map what point to center on in the display. We also set a ZoomLevel, which should encompass most of the state of New Jersey. Finally, we assign a mouse-click event to the map control. This allows us to handle the mouse click event and create a pushpin at the point where the user clicked the map. Our event handler for the click event will be ctlMap_MouseClick.

Before moving on to the code for the click event, where the pushpins are created, we need to create some basic state properties to control whether we are editing a pushpin’s text or just viewing them.

Adding State to the Map

Since we’ll be creating pushpins, which can be edited by the user, our map will actually have two states: Edit or View. When in View mode, the user can hover the mouse over a pushpin to display the popup text. When in Edit mode, the user can type new text in the pushpin, and hovering the mouse anywhere else will not affect the map until the editing has completed. We can control the state of our map with a simple state property as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected enum EditState
{
On,
Off
};

protected EditState editState = EditState.Off;

private Pushpin _editPin = null;

protected Pushpin editPin
{
get
{
return _editPin;
}
set
{
_editPin = value;

// Update the editable state. // If an editPin is available, we are in the editing state.
editState = editPin == null ? EditState.Off : EditState.On;
}
}

In the above code, we’ve defined a simple enumeration for the edit state, On or Off. We’ve also defined a variable to hold the pushpin we’re currently editing (when in Edit mode). We can therefore toggle the state flag by checking if our editPin has a value or not. When editPin has a value, we must be in Edit mode. Otherwise, we’re in View mode.

Handling the Map Click Event

When the user clicks the map, we’ll create a pushpin at the target location. The pushpin will display the latitude and longitude of the point clicked and allow the user to type a custom message. The pushpin will actually store the custom message within the Tag property. You could certainly create a class which wraps the pushpin and contains a property specific to storing the custom text, but for this example, we’ll hack the property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void ctlMap_MouseClick(object sender, MapMouseEventArgs e)
{
// The user clicked the map. If we're not in the edit state, add a new pushpin.
if (editState == EditState.Off)
{
// Convert the point to a Location.
Location location = ctlMap.Mode.ViewportPointToLocation(e.ViewportPoint);

// Create a pushpin.
Pushpin pin = new Pushpin();
pin.Location = location;
pin.Name = pin.Location.Latitude + ", " + pin.Location.Longitude;
pin.Tag = "Click the Pushpin to Edit";

// Add pushpin event handlers.
pin.MouseEnter += new MouseEventHandler(pin_MouseEnter);
pin.MouseLeave += new MouseEventHandler(pin_MouseLeave);
pin.MouseLeftButtonUp += new MouseButtonEventHandler(pin_MouseLeftButtonUp);

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

In the above code, the first thing we check is if we’re in Edit mode. If we’re in Edit mode, we do not want to add a new pushpin when the user clicks the map. The user will have to finish editing the pushpin text before adding new pushpins. Assuming the map is in View mode, we can proceed to add a new pushpin to the Microsoft Bing Silverlight map.

We create a pushpin by assigning a location. We then utilize the Name and Tag properties to store our custom text for the popup InfoBox. Finally, we register three event handlers for catching the mouse-hover event, mouse-leave event, and the mouse-click event. Remember, these events are on the pushpin - not the map. This will allow us to change the map to Edit mode when the user clicks the pushpin. It will also allow us to popup the InfoBox when the user hovers over the pushpin.

The final step to displaying the pushpin is to add it as a child element to the map, with ctlMap.Children.Add.

Let’s move on to handling the mouse-hover event, which pops up the InfoBox.

Popups on the Map

The MouseEnter event for a Silverlight Bing Map pushpin occurs when the user hovers the mouse over the pushpin. Since we’ve registered for this event on our pushpin, we can display the InfoBox from our MainPage.xaml when this occurs, by using the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void pin_MouseEnter(object sender, MouseEventArgs e)
{
// The user is hovering over a pushpin. // If we're not in the edit state, show the infobox.
if (editState == EditState.Off)
{
Pushpin pin = (Pushpin)sender;

// Use the pushpin's Name property for the title and the Tag // property for the description.
InfoboxTitle.Text = pin.Name;
InfoboxDescription.Text = pin.Tag.ToString();

// Show the infobox.
Infobox.Visibility = Visibility.Visible;
MapLayer.SetPosition(Infobox, pin.Location);
MapLayer.SetPositionOrigin(Infobox, PositionOrigin.BottomCenter);
MapLayer.SetPositionOffset(Infobox, new Point(0, -50));
}
}

As always, our first step is to verify that we’re in View mode. If we’re in Edit mode, we won’t show the popups until the user completes editing the pushpin text. Assuming we’re in View mode, we begin by obtaining the pushpin the user hovered over. This is provided in the sender parameter. We can then populate our InfoBox with the Name and Tag properties from the pushpin. We’ll then display the InfoBox by changing its Visibility value and set the display location to be next to the pushpin.

When the user hovers off of the pushpin, we can hide the InfoBox using the following code:

1
2
3
4
5
6
7
8
9
void pin_MouseLeave(object sender, MouseEventArgs e)
{
// The user has moved the mouse away from the pushpin. // If we're not in the edit state, hide the infobox.
if (editState == EditState.Off)
{
Infobox.Visibility = Visibility.Collapsed;
}
}

Clicking a Pushpin to Edit

We’ve registered for the MouseLeftButtonUp pushpin event, which lets us know when the user clicks a pushpin to begin editing its text. When this event occurs, we’ll change the state of the map to Edit by assigning a value to the temporary editPin property. This allows us to know which pushpin we’re editing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void pin_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// The user clicked a pushpin. If we're not in the edit state, // show the textbox to edit this pushpin's text.
if (editState == EditState.Off)
{
// Store a copy of the pushpin we're editing.
editPin = (Pushpin)sender;

// Show editable text box and close button.
InfoboxDescription.Visibility = Visibility.Collapsed;
InfoboxDescriptionEdit.Visibility = Visibility.Visible;
InfoboxDescriptionEdit.Text = editPin.Tag.ToString();
btnClose.Visibility = Visibility.Visible;
}
}

In the above code, we set the value for editPin to be the pushpin that was clicked, obtained via the sender parameter. We then display the InfoBox, including the editable TextBox control. We set the text for the TextBox to be the pushpin’s Tag property.

Saving the Pushpin Text

Once the user has finished changing the text in the InfoBox’s TextBox control, he can click the ‘X’ close button to dismiss the pushpin InfoBox and change the state of the map back to the View state. We’ve defined a click event for the btnClose control in the MainPage.xaml file, which is handled in the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void btnClose_Click(object sender, RoutedEventArgs e)
{
// The user clicked the 'X' close button from editing a pushpin's text.
// Hide editable text box and close button.
InfoboxDescription.Visibility = Visibility.Visible;
InfoboxDescriptionEdit.Visibility = Visibility.Collapsed;
btnClose.Visibility = Visibility.Collapsed;

Infobox.Visibility = Visibility.Collapsed;

// Save text changes in the pushpin.
editPin.Tag = InfoboxDescriptionEdit.Text;

// Clear out the edit pushpin in memory and reset state.
editPin = null;
}

In the above code, we first hide the InfoBox by setting its Visibility back to hidden. We then copy the value from the InfoBox’s TextBox (the text the user changed) to our editPin pushpin. Remember, editPin is a reference copy of the pushpin we’re currently editing. Any changes we make to editPin will be reflected in memory on the actual map’s pushpin. Therefore, we can simply set the editPin.Tag property to the TextBox.Text. The pushpin’s description has now been updated. The user may now hover over the pushpin with the mouse to display the new text.

Clearing the Map of Pushpins

The final control to implement is the Clear button. This allows the user to erase all pushpins from the map. When clicked, we’ll enumerate the UIElement objects on the map, and find those which are pushpins, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void btnClear_Click(object sender, RoutedEventArgs e)
{
// The user clicked the Clear button, erase all pushpins on the map.
List<UIElement> elementsToRemove = new List<UIElement>();

// Gather a list of all pushpins on the map.
foreach (UIElement element in ctlMap.Children)
{
if (element.GetType() == typeof(Pushpin))
{
Pushpin pin = (Pushpin)element;
if (pin != null)
{
elementsToRemove.Add(element);
}
}
}

// Remove the pushpins from the map.
foreach (UIElement element in elementsToRemove)
{
ctlMap.Children.Remove(element);
}
}

Notice in the above code, we make a first pass through the elements of the map to prepare a list of pushpin elements. We then enumerate through the list of pushpins and delete each one from the map. We actually require two separate loops since we cannot modify the map while enumerating its children.

Binding Things Together With WCF, the Entity Framework, and Linquify

At this point, we have a fully functional Silverlight Map web application, allowing the user to store unique information within pushpin locations throughout the map. We can further extend the functionality of the Silverlight C# ASP .NET web application by adding database support to load, save, and process the location information added by the user via pushpins.

In order to utilize a database with the Silverlight application, a WCF service would be required to serve as the intermediate between the Silverlight compact .NET project and the database project. The WCF service will reference a database project, utilizing Linquify and the Entity Framework.

The first piece to connect, for database functionality, is to assign an ID to each pushpin. This can be provided within a class wrapper for the pushpin, or for simplicity, one could re-use the pushpin.TabIndex property (for integer IDs). Using this method, creating a pushpin would appear as follows:

1
2
3
4
5
6
Pushpin pushpin = new Pushpin();
pushpin.TabIndex = id; // ID
pushpin.Location = new Location(latitude, longitude);
pushpin.Name = title;
pushpin.Tag = description;

This provides the core framework for saving and updating a pushpin in the database, based upon a primary key ID.

Saving a Pushpin to the Database

With a WCF web service implemented, functionality can be added to the btnClose_Click() event, which occurs when the user closes the pushpin’s InfoBox in Edit mode. At this point, the pushpin can be persisted and saved to the database as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void btnClose_Click(object sender, RoutedEventArgs e)
{
// The user clicked the 'X' close button from editing a pushpin's text.
HideInfoBox();

// Save text changes in the pushpin.
editPin.Tag = InfoboxDescriptionEdit.Text;

// Save to the database.
LocationViewModel locationViewModel = new LocationViewModel();
locationViewModel.LocationId = editPin.TabIndex; // ID
locationViewModel.Latitude = editPin.Location.Latitude;
locationViewModel.Longitude = editPin.Location.Longitude;
locationViewModel.Title = editPin.Name;
locationViewModel.Description = editPin.Tag.ToString();

_client.SaveLocationCompleted += new
EventHandler<SaveLocationCompletedEventArgs>(_client_SaveLocationCompleted);
_client.SaveLocationAsync(locationViewModel);
}

Notice, we’ve added code to the end of the btnClose_Click event to prepare a DTO object and call the WCF service’s SaveLocation method. When the Save method has completed, we need to obtain the ID, created in the database, and assign it to the pushpin, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
void _client_SaveLocationCompleted(object sender, SaveLocationCompletedEventArgs e)
{
if (editState == EditState.On)
{
// Update ID for this pushpin.
editPin.TabIndex = e.Result;

// Clear out the edit pushpin in memory and reset state.
editPin = null;
}
}

Remember, since the editPin references the currently edited pushpin in memory, we can modify its TabIndex property to update the ID of the pushpin with the primary key created in the database.

Loading Pushpins From the Database

We can extend the initialization of the Silverlight map by loading locations from the database, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void LoadPushPins()
{
_client.GetLocationsCompleted += new
EventHandler<GetLocationsCompletedEventArgs>(_client_GetLocationsCompleted);
_client.GetLocationsAsync();
}

void _client_GetLocationsCompleted(object sender, GetLocationsCompletedEventArgs e)
{
Pushpin pushpin = null;

foreach (LocationViewModel locationViewModel in e.Result)
{
// Create a pushpin.
pushpin = new Pushpin();
pushpin.TabIndex = locationViewModel.LocationId; // ID
pushpin.Location = new Location(locationViewModel.Latitude, locationViewModel.Longitude);
pushpin.Name = locationViewModel.Title;
pushpin.Tag = locationViewModel.Description;

// Add pushpin event handlers.
pushpin.MouseEnter += new MouseEventHandler(pin_MouseEnter);
pushpin.MouseLeave += new MouseEventHandler(pin_MouseLeave);
pushpin.MouseLeftButtonUp += new MouseButtonEventHandler(pin_MouseLeftButtonUp);

// Add the pushpin to the map.
ctlMap.Children.Add(pushpin);
}
}

The above method, LoadPushPins(), would be called in the end of the MainPage() constructor. We call the WCF service’s GetLocations() method, which in turn, uses Linquify and the Entity Framework to return a list of all locations in the database. For each location found, we create a pushpin on the Silverlight map. Each pushpin contains the primary key ID and text information, which can be edited and updated by the user.

The WCF Service Does the Hard Work

The WCF service code to handle the database requests, using Linquify and the Entity Framework, would appear as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[OperationContract]
public List<LocationViewModel> GetLocations()
{
List<LocationViewModel> locationList = new List<LocationViewModel>();
location location = new location();

// Convert each location to a LocationViewModel DTO.
foreach (location aLocation in location.ToList())
{
locationList.Add(CreateDTOFromLocation(aLocation));
}

return locationList;
}

[OperationContract]
public bool DeleteLocation(LocationViewModel locationViewModel)
{
location location = CreateLocationFromDTO(locationViewModel);

if (location.LocationId > 0)
{
return location.Delete();
}
else
{
return false;
}
}

[OperationContract]
public int SaveLocation(LocationViewModel locationViewModel)
{
location location = CreateLocationFromDTO(locationViewModel);
location.Save();

return location.LocationId;
}

While the database layer is handled using the .NET Entity Framework, we can take advantage of our Entity Framework business classes, generated with Linquify, to rapidly query the database to load and save location information from the pushpins.

While the above is just a short description of extending the Silverlight map web application with database functionality, you can read more about Creating Silverlight Web Applications with Linquify, LINQ to SQL, and WCF.

Conclusion

In this article, we’ve stepped through the process of implementing the Microsoft Bing Silverlight Map SDK. We’ve implemented the ability to add popup labels and editable pushpins to the map, allowing us to store unique information within each pushpin. With the above implementation, the mapping web application can be extended to support loading locations from a database with the Entity Framework, allowing updates by the user, and saving locations back to the database or over a network. The Microsoft Bing Silverlight Map SDK provides an easy way for developing complex mapping applications on the web.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share