Interactive Mapping Control for WPF

With ComponentOne's new Maps for WPF you get a viewing container control, C1Maps, for online maps. C1Maps provides smooth zooming, panning and mapping between screen and geographical coordinates. It can display map tiles from various sources, including Microsoft Live MapsTM or it can import geographic image files (KML). Then you can display various elements on top of the map layer positioned to specific geographic coordinates. Use the Items Layer for displaying arbitrary items like buttons, charts and labels. Use the Vector Layer for displaying vector data, like lines and polygons, whose vertices are geographically positioned. There's also the Virtual Layer which can be used for a virtualized display of items, meaning only items in view are downloaded from the server. Download Maps for WPF Now let's create a basic sample which displays push pins on a map. We will be able to add new pins by clicking, as well as delete and drag existing pins. First, create the project of your choice and drop a C1Maps control onto the page. For this sample, I created a WPF Browser Application (XBAP). XAML:

    <c1maps:C1Maps Name="c1Maps1" Zoom="1"/>  

I set the default Zoom level to 1. ComponentOne Maps includes 3 different built-in map sources using Live Maps including Aerial, Road and Hybrid views. By default it's using the Aerial view, so let's change it to use the Road view. Add the following code:

//Add shading parameter  
VirtualEarthRoadSource.UriFormat += "&shading=hill";  
//Set map source  
c1Maps1.Source = new VirtualEarthRoadSource();  

We will be using this image below for our push pin. We need to add this to the project in a folder called "Resources" and make sure the image's Build Action is set to Resource. Next, we will add this ImageSource to our Page.Resources. This will allow us to access our resource from code. Paste this XAML before the Grid control.

    <ImageSource x:Key="PushPin">Resources/Pushpin.png</ImageSource>  

Now onto the c1Maps...Add the following code:

//create items layer  
items = new C1MapItemsLayer();  
c1Maps1.MouseLeftButtonUp += new MouseButtonEventHandler(c1Maps1_MouseLeftButtonUp);  

for (int i = 0; i < 10; i++)  
        //create random coordinates  
    Point pt = new Point(-80 + rnd.Next(160), -80 + rnd.Next(160));  

This code adds a new Items Layer to our c1Maps control. Then we randomly generate 10 points for some default pins. The AddPin method looks like:

void AddPin(Point pt)  
    Image pin = new Image();  
    pin.Source = (ImageSource)FindResource("PushPin");  

    //Set Pinpoint in item  
    C1MapCanvas.SetPinpoint(pin, new Point(10, 31));  
    //Set geo coordinate of pin  
    C1MapCanvas.SetLatLong(pin, pt);  

    pin.MouseLeftButtonDown += new MouseButtonEventHandler(pin_MouseLeftButtonDown);  
    pin.MouseLeftButtonUp += new MouseButtonEventHandler(pin_MouseLeftButtonUp);  
    pin.MouseMove += new MouseEventHandler(pin_MouseMove);  


The AddPin method creates the Image element used for each pin. First it grabs the resource file we added earlier, and then it uses the C1MapCanvas panel to position the Image. The SetPinpoint method tells the control what coordinate within our item to position exactly over the geo coordinate. For example, we want the point of the red pushpin to display exactly on our geo coordinate rather than have the image centered over top. So we set the Pinpoint to (10, 31), which is roughly the point. Then we set the latitude and longitude of our geographic coordinate with the SetLatLong method.

User Interaction

In this sample we will handle various mouse actions. We will allow users to add a new pin by pressing Ctrl Click, delete a pin by pressing Alt Click, and move pins by pressing Shift Click.

void c1Maps1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)  
    if (Keyboard.Modifiers == ModifierKeys.Control)  

C1Maps provides four methods for converting among geographic, logical and screen coordinates. Here we are using the ScreenToGeographic method to convert the screen pixel coordinate relative to the top-left corner of the control to the latitude and longitude point. The mouse events below handle dragging and deleting the pins.

void pin_MouseMove(object sender, MouseEventArgs e)  
    if (currentPin != null)  
        Point pt = e.GetPosition(c1Maps1);  
        pt.X -= offset.X - 10;  
        pt.Y -= offset.Y - 31;  
        C1MapCanvas.SetLatLong(currentPin, c1Maps1.ScreenToGeographic(pt));  

Here we use the C1MapCanvas again to position our pin as the user drags the mouse. We have to take into account the pinpoint location as we adjust the offset of the image relative to the cursor.

void pin_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)  
    Image img = (Image)sender;  
    if (img != null)  
    currentPin = null;  

void pin_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)  
    Image img = (Image)sender;  

    if (Keyboard.Modifiers == ModifierKeys.Shift)  
        e.Handled = true;  
        if (img.CaptureMouse())  
            currentPin = img;  
            offset = e.GetPosition(currentPin);  
    else if (Keyboard.Modifiers == ModifierKeys.Alt)  
        e.Handled = true;  

The last thing we will do is provide a legend to the user. Add the following Border element immediately after the C1Maps control in XAML (it will display on top):

<Border VerticalAlignment="Top" Padding="2" HorizontalAlignment="Right" Margin="10" BorderThickness="2" BorderBrush="#FFC2C8D8" Background="#B9FFFFFF" CornerRadius="2">  
        <TextBlock Text="Ctrl+Click to Add" FontSize="10" />  
        <TextBlock Text="Shift+Click to Move" FontSize="10"/>  
        <TextBlock Text="Alt+Click to Delete" FontSize="10"/>  

The sample is complete! So now how can you extend this sample into something useful? You can take the collection of pins and collect their geographic coordinates and store them somewhere. The pin is just an image, but you could extend this further by providing more UI such as tooltips over each pin or maybe a drop-down menu. This sample used the Items Layer, but you can also use the Vector Layer which includes a baked-in placemark object model for displaying data values (see the "Marks" sample which installs with the control).

Greg Lutz

comments powered by Disqus