Visual Studio Light Edition

I noticed this blog post from Scott Hanselman the other day about Visual Studio Express 2012 for Windows Desktop. This post included a screenshot from the installation program. Something that really took me by the throat is this:

image

Notice that the Express edition of Visual Studio seems to require no less than 4.15 GB of hard disk space! So I decided to try out the Professional edition of Visual Studio just to find out that it needs 7.56 GB to install!

image

Ask yourself, how on earth can an IDE that requires 7.56 GB to install be fast enough to even be usable at all? What kind of monster machine is required to not even choke to death when I accidentally open a second instance of Visual Studio? We’ve all seen those dreadful white screens of death, right?

Yes, I know that we’re living in 2012 and that disk space is very cheap. Yes, I know that RAM memory grow on trees these days and that CPU power is growing increasingly. But is this kind of footprint justified for an IDE that proclaims productivity? Or is it just me?

After recovering from my amazement I tried installing SharpDevelop, an open-source IDE for building .NET applications.

image

SharpDevelop seems to be more than happy with just a mere 64 Mb of storage. I tried opening a large solution and it loads pretty darn fast. So, I should just shut up and go with SharpDevelop then? See, this is where I have to admit that I have a slight problem.

See, I depend heavily on Resharper for doing C# development. This amazing code-by-keystrokes tool is both a blessing and a curse. Any serious developer who builds .NET applications has to admit that whipping up some C# code without a tool like Resharper is incredibly painful, to say the least. This tool is truly a blessing and this is why I can’t go with SharpDevelop as my IDE of choice when building application for the .NET platform. But on the other hand, Resharper is not a stand-alone tool as it needs Visual Studio to host it. To me, this is a curse.

Visual Studio 2012 in it’s current form has become too heavily packed with features, which I don’t use anyway, so that’s it’s no longer usable for me to host Resharper. I’m no longer willing to make that tradeoff. I tried using Visual Studio 2012 with Resharper, but I turned back to Visual Studio 2010 after a short while. From a performance point of view, Visual Studio 2010 isn’t running great either. So I guess this is just a necessary evil that I have to overcome for building .NET 4.0 applications.

I understand that Microsoft’s business model is building platforms and tools for the world to use. It’s a business like any other business, focused on earning money. There’s nothing wrong with that. This is why Anders is so keen on statically typed languages, because they require more tooling and tooling is what brings in the money for his division. It’s as simple as that.

But it doesn’t have to be all bad! What if the Visual Studio development team would come up with a stripped down version of Visual Studio? Think about how awesome this could be. What should be in the box? Just the basics! The code editor, the solution explorer, the debugger and the ability to host add-ons. No designers, no SQL Server integration, no TFS integration, etc. … Just the most basic features. That’s it! The installation footprint should be no more than 500 Mb, ideally only 100Mb. Wouldn’t that be awesome? This would be would actually solve my current development needs!

I hereby tag my fellow Elegant Coder David Starr, who recently joined the Visual Studio team at Microsoft. You can do it David!  The best is yet to come!

So I’m eagerly awaiting the next release cycle of Visual Studio in order to see whether I’m up for a treat ;-).

Until next time.

Free Pluralsight Prism Training

I am excited to announce that Pluralsight and Microsoft’s Patterns & Practices team have joined forces to provide you with free access to over 4 hours of online Prism training.  That’s right, you heard me correctly. FREE online Prism training sponsored by the one and only Patterns & Practices team.  I know you have some question so let me answer the most common:

Q & A

Q1. When is this free training available?
A1. The entire weekend of Nov 12th 2011 through Nov 14th 2011.

Q2. Do I have to be a Pluralsight subscriber to get this awesome training?
A2. This training will be freely available to everyone. You do not have to be a subscriber.

Q3. What does the course cover?
A3. Well let’s take a look:

  • Getting started with Prism
  • Bootstrapper and Shell
  • Regions
  • Modules
  • Views
  • Communication
  • State-Based Navigation
  • View-Based Navigation

More course information.

Q4. Who is the author of this kick ass Prism course?
Q4. That would be me.

Q5. Is it really free?
Q5. You’re kidding right? Didn’t I already cover this part? Yes, it is free.

New and Improved

If you are a subscriber and have already watched the course, I would like to bring to your attention that there have been two new modules added.  These modules will cover everything you need to know in order to start using the navigation services provided by Prism.  This includes state-based navigation and view-based navigation.

Mark your calendar for Nov 12th and be sure to cram as much Prism knowledge into your brain as you can before the weekend ends.  Don’t worry, if you run out of time you can always ask me questions directly.  You can contact me either through Twitter (@brianlagunas) or through the Extended WPF Toolkit project site.  I hope you enjoy the training.

Extended WPF Toolkit–the updated PropertyGrid

The PropertyGrid control in the Extended WPF Toolkit was released in version 1.4.0 as beta. Since then I have had another release of version 1.5.0 in which the PropertyGrid was still in beta. Well it is beta no more! I have given the PropertyGrid a lot of love an affection over the past few weeks and it is finally ready for prime time. Unfortunately, you will have to wait until the release of version 1.6.0 to get an official version. Luckily for you, the source code is available for you to download and compile to get the latest and greatest PropertyGrid features. So what can you do with the PropertyGrid? Let’s look at some of the notable new and improved features.
propertygrid
Of course the PropertyGrid has the expected behavior of binding to an object instance and showing all the properties for your editing desires. It allows you sort display properties by category or alphabetically. It also provides a nice little search textbox so you can find exactly what you are looking for.

Specifying Properties

By default the PropertyGrid will autogenerate all the properties for a given object. Never fear, you can override this behavior by setting the AutoGenerateProperties property to False, and then provide a collection of PropertyDefinitions of the properties you would like to show.
specifyingproperties
<extToolkit:PropertyGrid x:Name=”_propertyGrid” Width=”450″ Margin=”10″
AutoGenerateProperties=”False”>
<!– Only the following properties will be displayed in the PropertyGrid –>
<extToolkit:PropertyGrid.PropertyDefinitions>
<extToolkit:PropertyDefinition Name=”FirstName” />
<extToolkit:PropertyDefinition Name=”FavoriteColor” />
<extToolkit:PropertyDefinition Name=”PetNames” />
</extToolkit:PropertyGrid.PropertyDefinitions>
</extToolkit:PropertyGrid>

Custom Editors

By default the PropertyGrid comes with 14 built-in editors:
  • CheckBoxEditor
  • CollectionEditor
  • ColorEditor
  • DateTimeUpDownEditor
  • DecimalUpDownEditor
  • DoubleUpDownEditor
  • EnumComboBoxEditor
  • FontComboBoxEditor
  • IntegerUpDownEditor
  • ItemsSourceEditor
  • PrimitiveTypeCollectionEditor
  • TextBlockEditor
  • TextBoxEditor
  • TimeSpanEditor
These editors don’t meet your needs? No problem! You can override the default editors with your own custom editors with a DataTemplate. Simply define an EditorDefinition that either targets a Type, property name, or both and set the EditorDefinition.EditorTemplate to an instance of a DataTemplate. Be sure to bind your custom editor to the bound property item’s Value property.
<extToolkit:PropertyGrid x:Name=”_propertyGrid1″ Width=”450″ Margin=”10″>
<extToolkit:PropertyGrid.EditorDefinitions>

<!– This EditorDefinition will provide a TextBox to any property that is of type HorizontalAlignment, replacing the default ComboBox editor. –>
<extToolkit:EditorDefinition TargetType=”{x:Type HorizontalAlignment}“>
<extToolkit:EditorDefinition.EditorTemplate>
<DataTemplate>
<TextBox Background=”Green” Text=”{Binding Value}” /> <!– Always bind your editor’s value to the bound property’s Value –>
</DataTemplate>
</extToolkit:EditorDefinition.EditorTemplate>
</extToolkit:EditorDefinition>

<!– This EditorDefinition will provide a TextBlock to any property that has any of the defined property names, replacing the default editor. –>
<extToolkit:EditorDefinition>
<extToolkit:EditorDefinition.PropertiesDefinitions>
<extToolkit:PropertyDefinition Name=”Age” />
<extToolkit:PropertyDefinition Name=”WritingFont” />
<extToolkit:PropertyDefinition Name=”Spouse” />
</extToolkit:EditorDefinition.PropertiesDefinitions>
<extToolkit:EditorDefinition.EditorTemplate>
<DataTemplate>
<TextBlock Background=”Yellow” Text=”{Binding Value}” />
</DataTemplate>
</extToolkit:EditorDefinition.EditorTemplate>
</extToolkit:EditorDefinition>

<!– This EditorDefinition will provide a TextBox to any property that is of type Boolean and that has any of the defined property names, replacing the default editor. –>
<extToolkit:EditorDefinition TargetType=”{x:Type sys:Boolean}“>
<extToolkit:EditorDefinition.PropertiesDefinitions>
<extToolkit:PropertyDefinition Name=”DateOfBirth” />
<extToolkit:PropertyDefinition Name=”LastName” />
</extToolkit:EditorDefinition.PropertiesDefinitions>
<extToolkit:EditorDefinition.EditorTemplate>
<DataTemplate>
<TextBox Background=”Red” Text=”{Binding Value}” />
</DataTemplate>
</extToolkit:EditorDefinition.EditorTemplate>
</extToolkit:EditorDefinition>

</extToolkit:PropertyGrid.EditorDefinitions>
</extToolkit:PropertyGrid>

Don’t like DataTemplates? No problem! You can supply editors for a property by using the System.ComponentModel.EditorAttribute. In order to provide an editor with an attribute, the editor MUST implement the ITypeEditor interface. Your editor can be a simple class or a complex UserControl.
customeditor_attributes
public class Person
{
[Category(“Information”)]
[DisplayName(“First Name”)]
[Description(“This property uses a TextBox as the default editor.”)]
//This custom editor is a Class that implements the ITypeEditor interface
[Editor(typeof(FirstNameEditor), typeof(FirstNameEditor))]
public string FirstName { get; set; }
[Category(“Information”)]
[DisplayName(“Last Name”)]
[Description(“This property uses a TextBox as the default editor.”)]
//This custom editor is a UserControl that implements the ITypeEditor interface
[Editor(typeof(LastNameUserControlEditor), typeof(LastNameUserControlEditor))]
public string LastName { get; set; }
}
Your editors can be created as a class:
//Custom editors that are used as attributes MUST implement the ITypeEditor interface.
public class FirstNameEditor : Microsoft.Windows.Controls.PropertyGrid.Editors.ITypeEditor
{
public FrameworkElement ResolveEditor(Microsoft.Windows.Controls.PropertyGrid.PropertyItem propertyItem)
{
TextBox textBox = new TextBox();
textBox.Background = new SolidColorBrush(Colors.Red);
//create the binding from the bound property item to the editor
var _binding = new Binding(“Value”); //bind to the Value property of the PropertyItem
_binding.Source = propertyItem;
_binding.ValidatesOnExceptions = true;
_binding.ValidatesOnDataErrors = true;
_binding.Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay;
BindingOperations.SetBinding(textBox, TextBox.TextProperty, _binding);
return textBox;
}
}
Or as a complex UserControl:
<UserControl x:Class=”Samples.Modules.PropertyGrid.LastNameUserControlEditor”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
x:Name=”_uc”>
<StackPanel>
<TextBox Text=”{Binding Value, ElementName=_uc}” Background=”YellowGreen” />
<Button Click=”Button_Click”>Clear</Button>
</StackPanel>
</UserControl>
public partial class LastNameUserControlEditor : UserControl, ITypeEditor
{
public LastNameUserControlEditor()
{
InitializeComponent();
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(“Value”, typeof(string), typeof(LastNameUserControlEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Value = string.Empty;
}
public FrameworkElement ResolveEditor(Microsoft.Windows.Controls.PropertyGrid.PropertyItem propertyItem)
{
Binding binding = new Binding(“Value”);
binding.Source = propertyItem;
binding.Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay;
BindingOperations.SetBinding(this, LastNameUserControlEditor.ValueProperty, binding);
return this;
}
}

Custom ItemsSource

Sometimes it is desirable to want to provide a collection of values represented by a ComboBox for a given property. The PropertyGrid supports this scenario by creating a class that implements the IItemsSource interface and decorating your property with the ItemsSourceAttribute.
custom_itemssource
public class Person
{
[Category(“Writing”)]
[DisplayName(“Writing Font Size”)]
[Description(“This property uses the DoubleUpDown as the default editor.”)]
[ItemsSource(typeof(FontSizeItemsSource))]
public double WritingFontSize { get; set; }
}
public class FontSizeItemsSource : IItemsSource
{
public IList<object> GetValues()
{
List<object> sizes = new List<object>()
{
5.0,5.5,6.0,6.5,7.0,7.5,8.0,8.5,9.0,9.5,10.0,12.0,14.0,16.0,18.0,20.0
};
return sizes;
}
}

Expandable Properties

Wait a minute… I’m not done yet. Do you have a property of a complex object that you want to expand in the PropertyGrid? You do? Well then you want to pay attention to this. The PropertyGrid supports this scenario and allows you to drill down into a property’s heirarchy. To enable this behavior you must decorate your property with the ExpandableObject attribute.
expandableobject
public class Person
{
[Category(“Information”)]
[DisplayName(“First Name”)]
[Description(“This property uses a TextBox as the default editor.”)]
public string FirstName { get; set; }
[Category(“Conections”)]
[Description(“This property is a complex property and has no default editor.”)]
[ExpandableObject]
public Person Spouse { get; set; }
}

Conclusion

As you can see, I have put a lot of time and effort into this control to make it as feature rich and flexible as possible. This WPF PropertyGrid is freely available in the Extended WPF Toolkit. All I ask, is that if you use the Extended WPF Toolkit, can you please rate the project and leave feedback with your impressions. It is always nice to receive feedback to know if my efforts are paying off. Enjoy!

Extended WPF Toolkit–using the BusyIndicator

As you may know, I am the author of a WPF control suite call the Extended WPF Toolkit.  I also spend tons of time on various forums helping people solve problems with their WPF applications.  Lately there have been a number of questions regarding the BusyIndicator control.  The majority of questions deal with using it in a multi-threaded environment.  So I thought the best way to address the masses is to write up a post demonstrating the proper use of the BusyIndicator control.

Let’s begin by defining what the BusyIndicator is used for.  The BusyIndicator is used to give an application user a visual indicator that a long running process is occurring.  So from this definition we can make two assumptions. One; There must be a mechanism for notifying the BusyIndicator when the long running process begins and when it ends.  Two; the long running process will have to occur on a separate thread as to not block the UI.

Anatomy of the BusyIndicator

The first thing you need to do when using any control inside the Extended WPF Toolkit is add a namespace declaration to the consuming view.

<Window x:Class="BusyIndicatorDemo.MainWindow"
        xmlns:extToolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit/extended"
       

The BusyIndicator is a ContentControl.  What this means is that the BusyIndicator can contain a single child element within it’s open and closing tags.  For example:

<extToolkit:BusyIndicator>
    <Grid>
        <Button>Start Process</Button>
    </Grid>
</extToolkit:BusyIndicator>

Looking at this code snippet you can see that the Grid is the BusyIndicator’s Content.  If you were to place anything outside of this Grid element you would receive an exception.

The BusyIndicator has two visual states:

Not Busy:

image

Busy:

image

Toggling between these states is as simply as setting a single property called IsBusy.

<extToolkit:BusyIndicator IsBusy="True" >
    <Grid>
        <Button>Start Process</Button>
    </Grid>
</extToolkit:BusyIndicator>

When the BusyIndicator.IsBusy property is set to True, the BusyIndicator becomes visible and prevents any user interaction with the BusyIndicator’s Content.  So any elements that you have defined within the Content of the BusyIndicator will be disabled.  When the IsBusy property is set to False, the Content re-enables and user interaction can continue.

The Common Problem

The most common problem I see when people are having trouble using the BusyIndicator is that they are doing everything on the UI thread.  Let’s look at an example:

<extToolkit:BusyIndicator x:Name="_busyIndicator" >
    <Grid>
        <Button Click="StartProcess">Start Process</Button>
    </Grid>
</extToolkit:BusyIndicator>

The event handler for the button looks like this:

private void StartProcess(object sender, RoutedEventArgs e)
{
    //show BusyIndicator
    _busyIndicator.IsBusy = true;
    
    //long running process
    for (int i = 0; i < 100; i++)
    {
        System.Threading.Thread.Sleep(50);
    }

    //hide BusyIndicator
    _busyIndicator.IsBusy = false;
}

Now the assumption is that first the BusyIndicator will be shown, then the long running process will occur, and lastly the BusyIndicator will be hidden.  To most people’s surprise it doesn’t work.  The BusyIndicator never shows and the UI is not responsive.  Do you see the problem?  The problem is that everything is running on the UI thread.

Solution

As we have already stated, the long running process must occur on a separate thread.  The UI thread needs to stay responsive while the process is running.  The most common method to place a long running process on a separate thread is to use the BackgroundWorker.

Let’s modify the event handler to utilize a BackgroundWorker.

private void StartProcess(object sender, RoutedEventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    //this is where the long running process should go
    worker.DoWork += (o, ea) =>
        {
            //no direct interaction with the UI is allowed from this method
            for (int i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(50);
            }
        };
    worker.RunWorkerCompleted += (o, ea) =>
        {
            //work has completed. you can now interact with the UI
            _busyIndicator.IsBusy = false;
        };
    //set the IsBusy before you start the thread
    _busyIndicator.IsBusy = true;
    worker.RunWorkerAsync();
}

Examine the code.  First we create an instance of a BackgroundWorker and add delegates for the DoWork and RunWorkerCompleted events.  The DoWork hander is where the long running process should go.  NO DIRECT INTERACTION WITH THE UI IS ALLOWED.  The RunWorkerCompleted handler is code that will run when the long running process has completed.  This is where you can interact with the UI again, meaning that this would be a perfect place to set the IsBusy property to False.  After the long running process has finished.  Now pay special attention to where we are calling RunWorkerAsync.  Just before that call, we are setting the IsBusy property to true.  Then the call to RunWorkerAsync is made.  This will show the BusyIndicator and then start the long running process.  When the process is complete the BusyIndicator is hidden again.  Perfect!

Common Problem 2

The next common problem is dealing with using data that is generated on the background thread in the UI.  Well if you read my post on multi-threading then you should already know how to solve this one.  If you haven’t, I will show you again.  This scenario requires that our UI have a ListBox control that is populated with items on a background thread.  While the items are being populated, the BusyIndicator should show the user something is happening and that they need to be patient.  Let’s modify our UI and our code behind to support his new scenario:

<extToolkit:BusyIndicator x:Name="_busyIndicator" >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox x:Name="_listBox" />
        <Button Grid.Row="1" Click="StartProcess">Start Process</Button>
    </Grid>
</extToolkit:BusyIndicator>

private void StartProcess(object sender, RoutedEventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += (o, ea) =>
        {
            _busyIndicator.IsBusy = true;

            List<String> listOfString = new List<string>();
            for (int i = 0; i < 100; i++)
            {
                listOfString.Add(String.Format("Item: {0}", i));
            }

            //BAD MOJO
            _listBox.ItemsSource = listOfString;
            _busyIndicator.IsBusy = false;
        };            
    worker.RunWorkerAsync();
}

This is similar to what the problem cases look like.  This code will most definitely fail.  Remember, you cannot access any UI elements on a separate thread.  Now let’s modify this code to make it run correctly.

Solution

private void StartProcess(object sender, RoutedEventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += (o, ea) =>
        {
            List<String> listOfString = new List<string>();
            for (int i = 0; i < 10000000; i++)
            {
                listOfString.Add(String.Format("Item: {0}", i));
            }

            //use the Dispatcher to delegate the listOfStrings collection back to the UI
            Dispatcher.Invoke((Action)(() => _listBox.ItemsSource = listOfString));
        };
    worker.RunWorkerCompleted += (o, ea) =>
    {
        _busyIndicator.IsBusy = false;
    };
    _busyIndicator.IsBusy = true;
    worker.RunWorkerAsync();
}

First we had to remove any reference to a UI element from the DoWork handler.  Now in order to propagate the generate list of strings back to the ListBox we enlist the help of the Dispatcher.  The Dispatcher will take the collection of strings, and in a thread safe way, give them to the ListBox for consumption.  Our BusyIndicator will be shown while the collection is being generated, and then be hidden when the process has been completed.

That about wraps it up.  These are the most common troubles developers have had using the BusyIndicator control.  If you have different problems that you haven’t been able to solve, feel free to post your question in the Discussions page of the project site.  You never know, it may be worth blogging about.

Mapping a Route with Directions using the Bing Maps WPF Control (Beta)

So far we have built an earthquake application, mapped an address using geocoding, and even Prism-fied the Bing Maps WPF control.  Now let’s see how to map a route with turn-by-turn directions.  By now it should be no surprise that you will need to complete the following steps before you can create this application.

  1. Get the Bing Maps WPF control.
  2. Get a Bing Maps API key.

You MUST have a Bing Maps API key in order to use any of the SOAP services.

For this application we will be using the Geocode Service and the Route Service.  The Geocode service is used to match addresses, places, and geographic entities to latitude and longitude coordinates on the map, as well as return location information for a specified latitude and longitude coordinate.  The Route service is used to generate routes and driving directions based on locations or waypoints.

Now that you have that out of the way let’s write an application. Create a new WPF application targeting the .NET 4.0 framework. Add a reference to the Microsoft.Maps.MapControl.WPF.dll. This will most likely be located in Program Files or Program Files (x86) –> Bing Maps WPF Control –> Beta –> Libraries.

Open up your App.xaml. You need to add an ApplicationIdCredentialsProvider as a resource and enter your ApplicationId:

<Application x:Class=”BingMapsCalculateRouteDemo.App”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:bing=”clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF”
StartupUri=”MainWindow.xaml”>
<Application.Resources>
<bing:ApplicationIdCredentialsProvider x:Key=”MyCredentials” ApplicationId=”Your API Key” />
</Application.Resources>
</Application>

We need to add the Geocode and Route service to our application.  First, let’s start by adding the Geocode service.  Right click the project and select “Add Service Reference”.  Use the following address:

http://dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc

Give your service a name. I called mine BingServices.  Now we need to add the Route service, but we don’t want to add a separate service reference.  Ideally we would have a single service that contained both the Geocode and Route services.  Luckily there is a trick we can use to do just that.  Select the Project and click the “Show All Files” button at the top of the Solution Explorer.  Expand the Service Reference folder and then expand the BingServices service.  Look for a file called Reference.svcmap. Open the References.svcmap and locate the MetadataSource node.  It will loko something like this:

<MetadataSources>
<MetadataSourceAddress=http://dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc/mexProtocol=mexSourceId=1 />
</MetadataSources>

Simply add a new MetatdataSource as follows:

<MetadataSources>
<MetadataSourceAddress=http://dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc/mexProtocol=mexSourceId=1 />
<MetadataSourceAddress=http://dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc/mexProtocol=mexSourceId=2 />
</MetadataSources>

As you can see we added a new MetadataSource that points to the Route service and we gave it a SourceId of 2.  Now right click the service and select “Update Service Reference”.  This will generate a single service proxy for both the Geocode and Route services.  Pretty slick huh?

The next step will be to create a ViewModel that will contain an ICommand that will execute the route calculation, properties that will represent a “To” address and a “From” address.  We will also need a property for the RouteResult and for the directions we generate.  Lets first create our Direction object:

public class Direction
{
public string Description { get; set; }
public Location Location { get; set; }
}

Now we will need a make-shift delegate command (do not use in production code):

public class DelegateCommand : ICommand
{
private Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_execute.Invoke();
}
}

Now on to the ViewModel itself:

public class RouteViewModel: INotifyPropertyChanged
{
public ICommand CalculateRouteCommand { get; private set; }    private string _from = “Meridian, ID”;
public string From
{
get { return _from; }
set
{
_from = value;
OnPropertyChanged(“From”);
}
}

private string _to = “Boise, ID”;
public string To
{
get { return _to; }
set
{
_to = value;
OnPropertyChanged(“To”);
}
}

private BingServices.RouteResult _routeResult;
public BingServices.RouteResult RouteResult
{
get { return _routeResult; }
set
{
_routeResult = value;
OnPropertyChanged(“RouteResult”);
}
}
private ObservableCollection<Direction> _directions;
public ObservableCollection<Direction> Directions
{
get { return _directions; }
set
{
_directions = value;
OnPropertyChanged(“Directions”);
}
}

public RouteViewModel()
{
CalculateRouteCommand = new DelegateCommand(CalculateRoute);
}

private void CalculateRoute()
{
var from = GeocodeAddress(From);
var to = GeocodeAddress(To);

CalculateRoute(from, to);
}

private BingServices.GeocodeResult GeocodeAddress(string address)
{
BingServices.GeocodeResult result = null;

using (BingServices.GeocodeServiceClient client = new BingServices.GeocodeServiceClient(“CustomBinding_IGeocodeService”))
{
BingServices.GeocodeRequest request = new BingServices.GeocodeRequest();
request.Credentials = new Credentials() { ApplicationId = (App.Current.Resources[“MyCredentials”] as ApplicationIdCredentialsProvider).ApplicationId };
request.Query = address;
result = client.Geocode(request).Results[0];
}

return result;
}

private void CalculateRoute(BingServices.GeocodeResult from, BingServices.GeocodeResult to)
{
using (BingServices.RouteServiceClient client = new BingServices.RouteServiceClient(“CustomBinding_IRouteService”))
{
BingServices.RouteRequest request = new BingServices.RouteRequest();
request.Credentials = new Credentials() { ApplicationId = (App.Current.Resources[“MyCredentials”] as ApplicationIdCredentialsProvider).ApplicationId };
request.Waypoints = new ObservableCollection<BingServices.Waypoint>();
request.Waypoints.Add(ConvertResultToWayPoint(from));
request.Waypoints.Add(ConvertResultToWayPoint(to));

request.Options = new BingServices.RouteOptions();
request.Options.RoutePathType = BingServices.RoutePathType.Points;

RouteResult = client.CalculateRoute(request).Result;
}

GetDirections();
}

private void GetDirections()
{
Directions = new ObservableCollection<Direction>();

foreach (BingServices.ItineraryItem item in RouteResult.Legs[0].Itinerary)
{
var direction = new Direction();
direction.Description = GetDirectionText(item);
direction.Location = new Location(item.Location.Latitude, item.Location.Longitude);
Directions.Add(direction);
}
}

private static string GetDirectionText(BingServices.ItineraryItem item)
{
string contentString = item.Text;
//Remove tags from the string
Regex regex = new Regex(“<(.|\n)*?>”);
contentString = regex.Replace(contentString, string.Empty);
return contentString;
}

private BingServices.Waypoint ConvertResultToWayPoint(BingServices.GeocodeResult result)
{
BingServices.Waypoint waypoint = new BingServices.Waypoint();
waypoint.Description = result.DisplayName;
waypoint.Location = result.Locations[0];
return waypoint;
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Let’s walk though the CalculateRoute Method.  First we geocode the “From” and “To” properties the same way we did in the geocoding post.  Once we have our From and To locations we use those results to generate a RouteRequest and send that request to the Route service.  The service will generate a result and we set our RouteResult property accordingly.  This property will be used in data binding shortly.  Next we generate an ObservableCollection<Direction> by looping through all the ItineraryItems in the result.

Now that we have generate all of our data, we need to create a View that supports and displays this data, but before we do that we need to create some attached properties.  Why do you ask?  Well because as it stands, the Bing Maps WPF control is not data binding friendly.  So we need to enable data binding with the use of attached properties.  We will need an attached property for the Route result which will be responsible for drawing the route line on the map.  We will also need one for the MapLayer that will be used to contain the route line.  Here is the code:

public class MapInteractivity
{
#regionRouteResult    public static readonly DependencyProperty RouteResultProperty = DependencyProperty.RegisterAttached(“RouteResult”, typeof(BingServices.RouteResult), typeof(MapInteractivity), new UIPropertyMetadata(null, OnRouteResultChanged));
public static BingServices.RouteResult GetRouteResult(DependencyObject target)
{
return (BingServices.RouteResult)target.GetValue(RouteResultProperty);
}
public static void SetRouteResult(DependencyObject target, BingServices.RouteResult value)
{
target.SetValue(RouteResultProperty, value);
}

private static void OnRouteResultChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
OnRouteResultChanged((Map)o, (BingServices.RouteResult)e.OldValue, (BingServices.RouteResult)e.NewValue);
}

private static void OnRouteResultChanged(Map map, BingServices.RouteResult oldValue, BingServices.RouteResult newValue)
{
MapPolyline routeLine = new MapPolyline();
routeLine.Locations = new LocationCollection();
routeLine.Opacity = 0.65;
routeLine.Stroke = new SolidColorBrush(Colors.Blue);
routeLine.StrokeThickness = 5.0;

foreach (BingServices.Location loc in newValue.RoutePath.Points)
{
routeLine.Locations.Add(new Location(loc.Latitude, loc.Longitude));
}

var routeLineLayer = GetRouteLineLayer(map);
if (routeLineLayer == null)
{
routeLineLayer = new MapLayer();
SetRouteLineLayer(map, routeLineLayer);
}

routeLineLayer.Children.Clear();
routeLineLayer.Children.Add(routeLine);

//Set the map view
LocationRect rect = new LocationRect(routeLine.Locations[0], routeLine.Locations[routeLine.Locations.Count – 1]);
map.SetView(rect);
}

#endregion //RouteResult

#region RouteLineLayer

public static readonly DependencyProperty RouteLineLayerProperty = DependencyProperty.RegisterAttached(“RouteLineLayer”, typeof(MapLayer), typeof(MapInteractivity), new UIPropertyMetadata(null, OnRouteLineLayerChanged));
public static MapLayer GetRouteLineLayer(DependencyObject target)
{
return (MapLayer)target.GetValue(RouteLineLayerProperty);
}
public static void SetRouteLineLayer(DependencyObject target, MapLayer value)
{
target.SetValue(RouteLineLayerProperty, value);
}
private static void OnRouteLineLayerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
OnRouteLineLayerChanged((Map)o, (MapLayer)e.OldValue, (MapLayer)e.NewValue);
}
private static void OnRouteLineLayerChanged(Map map, MapLayer oldValue, MapLayer newValue)
{
if (!map.Children.Contains(newValue))
map.Children.Add(newValue);
}

#endregion //RouteLineLayer
}

The RouteResult property simply creates a MapPolyline, loops through all the points in the result and adds the locations to the route line.  It then gets the RouteLineLayer and adds it to the map.  Lastly, it sets the map’s view to best fit the route line.

Now we are ready for the View:

<Window x:Class=”BingMapsCalculateRouteDemo.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:bing=”clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF”
xmlns:core=”clr-namespace:BingMapsCalculateRouteDemo.Core”
Title=”MainWindow” Height=”600″ Width=”800″>    <Window.Resources>
<DataTemplate x:Key=”RouteTemplate”>
<Ellipse Width=”12″ Height=”12″ Fill=”Red” Opacity=”0.8″
bing:MapLayer.Position=”{Binding Location}
bing:MapLayer.PositionOrigin=”Center”
Tag=”{Binding}
MouseEnter=”Route_MouseEnter”
MouseLeave=”Route_MouseLeave”/>
</DataTemplate>
</Window.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=”Auto” />
<ColumnDefinition Width=”*” />
</Grid.ColumnDefinitions>

<StackPanel MinWidth=”150″ Margin=”10″>
<TextBox Text=”{Binding From}” />
<TextBox Text=”{Binding To}“/>
<Button Content=”Get Route” Command=”{Binding CalculateRouteCommand}“/>
</StackPanel>

<bing:Map Grid.Column=”1″ Mode=”AerialWithLabels” HorizontalAlignment=”Stretch” VerticalAlignment=”Stretch” AnimationLevel=”Full”
core:MapInteractivity.RouteResult=”{Binding RouteResult}
core:MapInteractivity.RouteLineLayer=”{Binding ElementName=RouteLineLayer}”
CredentialsProvider=”{StaticResource MyCredentials}” >

<bing:MapLayer x:Name=”RouteLineLayer” />

<bing:MapLayer>
<bing:MapItemsControl ItemsSource=”{Binding Directions}
ItemTemplate=”{StaticResource RouteTemplate}“/>
</bing:MapLayer>

<bing:MapLayer x:Name=”ContentPopupLayer”>
<Grid x:Name=”ContentPopup” Visibility=”Collapsed” Background=”White”>
<StackPanel Margin=”15″>
<TextBlock x:Name=”ContentPopupText” FontSize=”12″ FontWeight=”Bold” TextWrapping=”Wrap” />
</StackPanel>
</Grid>
</bing:MapLayer>
</bing:Map>
</Grid>
</Window>

Oh and don’t forget the code behind:

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new RouteViewModel();
}    private void Route_MouseEnter(object sender, MouseEventArgs e)
{
FrameworkElement pin = sender as FrameworkElement;
MapLayer.SetPosition(ContentPopup, MapLayer.GetPosition(pin));
MapLayer.SetPositionOffset(ContentPopup, new Point(20, -15));

var location = (Direction)pin.Tag;

ContentPopupText.Text = location.Description;
ContentPopup.Visibility = Visibility.Visible;
}

private void Route_MouseLeave(object sender, MouseEventArgs e)
{
ContentPopup.Visibility = Visibility.Collapsed;
}
}

Now let’s view the fruits of our labor at runtime.

image

As you can see, given a “From” and “To” address, we can draw a route line and provide turn-by-turn directions as the user hovers over the direction pins.  Pretty neat huh?  And it wasn’t too difficult to pull off.  As always you can Download the Source and start playing.

Prism-fy the Bing Maps WPF Control (Beta)

If you know me or know of me you are aware that I am a big Prism advocate/evangelist.  You may have also noticed that I have been playing around with the new Bing Maps WPF control.  So it shouldn’t surprise you that I would find a way to compose a Bing Map of loosely coupled MapLayers at runtime using Prism.

The concept is simple.  We want a Bing Maps application that can be extended at runtime.  By extended, I mean that I want the ability to add new elements/modules to the Map at runtime.  The important thing about these elements/modules is that they can come from anywhere and they should be loosely coupled from the Map as well as other elements/modules that may exist on the Map.

Luckily for us this is extremely simple to accomplish.  All we have to do is create a custom RegionAdapter, register it with our Prism application, and then apply it to our Bing Map control.  So let’s start with the RegionAdapter.

public class MapRegionAdapter : RegionAdapterBase<Map>
{
    public MapRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
        : base(regionBehaviorFactory)
    {

    }

    protected override void Adapt(IRegion region, Map regionTarget)
    {
        region.Views.CollectionChanged += (s, e) =>
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                foreach (FrameworkElement element in e.NewItems)
                    regionTarget.Children.Add(element);
            }
            else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            {
                foreach (FrameworkElement element in e.OldItems)
                    if (regionTarget.Children.Contains(element))
                        regionTarget.Children.Remove(element);
            }
        };
    }

    protected override IRegion CreateRegion()
    {
        return new AllActiveRegion();
    }
}

Now in the Bootstrapper we need to register our mapping.

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    RegionAdapterMappings mappings = base.ConfigureRegionAdapterMappings();
    mappings.RegisterMapping(typeof(Map), Container.Resolve<MapRegionAdapter>());
    return mappings;
}

Now we simply give the Map a region name. (this is actually all the shell has in it)

<Grid>
    <bing:Map prism:RegionManager.RegionName="{x:Static inf:RegionNames.MapRegion}" Center="40,-95" ZoomLevel="4" />
</Grid>

That is all there is to it.  You can now start injecting modules onto the Map at runtime.  Lets look at my two modules I am using as an example.  Here is the structure of my application:

image

  • ModuleA will inject a MapPolygon in the Shape of Texas.
  • ModuleB is the Earthquake application we built in an earlier post.
  • Infrastructure is the project where shared code goes, in this case our MapRegionAdapter
  • BingMapsPrismfiedDemo is of course our shell project.

This is what the application looks like at runtime when both modules have been injected into it.

image

Now I can easily add more layers to this map as I see fit.  As always, Download the Source and start playing.  You may want to add MEF support as well.

Mapping an Address with the Bing Maps WPF Control (Beta)

In my last post I showed you how to get started using the Bing Maps WPF control by creating an earthquake application.  Now I want to introduce you to the Bing Maps SOAP Services.  The Bing Maps SOAP Services is a set of programmable SOAP services that allow you to integrate maps and imagery, driving directions, distance calculations, and other location intelligence into your applications, business processes, and Web sites.

Let’s start with the Geocode Service which is used to match addresses, places, and geographic entities to latitude and longitude coordinates on the map, as well as return location information for a specified latitude and longitude coordinate.  We will create a simple application that allows you to enter an address and place a pushpin at that location.  First thing is first:

  1. Get the Bing Maps WPF control.
  2. Get a Bing Maps API key.

You MUST have a Bing Maps API key in order to use any of the SOAP services.  Now that you have that out of the way let’s write an application. Create a new WPF application targeting the .NET 4.0 framework. Add a reference to the Microsoft.Maps.MapControl.WPF.dll. This will most likely be located in Program Files or Program Files (x86) –> Bing Maps WPF Control –> Beta –> Libraries.

Open up your App.xaml.  You need to add an ApplicationIdCredentialsProvider as a resource and enter your ApplicationId:

<Application x:Class="BingMapsGeocodeDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:bing="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <bing:ApplicationIdCredentialsProvider x:Key="MyCredentials" ApplicationId="Your API Key" />
    </Application.Resources>
</Application>

Now create a very simple UI that will accept an address and a button to perform the geocoding:

<Window x:Class="BingMapsGeocodeDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:bing="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
        Title="MainWindow" Height="600" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Margin="10" >
            <TextBox x:Name="_txtAddress" MinWidth="250" />
            <Button Content="Map It" IsDefault="True"/>
        </StackPanel>

        <bing:Map Grid.Row="1" CredentialsProvider="{StaticResource MyCredentials}" Center="40,-95"
                  ZoomLevel="4" AnimationLevel="Full" >
        </bing:Map>
    </Grid>
</Window>

The next thing we need to do is add a new Service Reference to the project and point to the following service:

http://dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc

Give your service a name (I called mine BingMapsService) and just for fun, in the “Add Service Reference” dialog, click the “Advanced” button and click “Generate asynchronous operations”.

Next we need to create a ViewModel to support our View.  This is a WPF application after all.  This ViewModel will need a command for the button that will perform the geocoding, and a property that will represent the result of the service that the UI will use to map the address.  It should resemble something like this:

public class GeocodeViewModel : INotifyPropertyChanged
{
    public ICommand GeocodeAddressCommand { get; private set; }

    private BingMapsService.GeocodeResult _geocodeResult;
    public BingMapsService.GeocodeResult GeocodeResult
    {
        get { return _geocodeResult; }
        set
        {
            _geocodeResult = value;
            OnPropertyChanged("GeocodeResult");
        }
    }

    public GeocodeViewModel()
    {
        GeocodeAddressCommand = new DelegateCommand<String>(GeocodeAddress);
    }

    private void GeocodeAddress(string address)
    {

    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

I want to point out that the DelegateCommand<T> is a very simple ICommand implementation and should NOT be used in production applications.  Here is the code:

public class DelegateCommand<T> : ICommand
{
    private Action<T> _execute;

    public DelegateCommand(Action<T> execute)
    {
        _execute = execute;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        _execute.Invoke((T)parameter);
    }
}

Let’s hook our UI up to our command.  it should look something like this:

<StackPanel Orientation="Horizontal" Margin="10" >
            <TextBox x:Name="_txtAddress" MinWidth="250" />
            <Button Content="Map It" IsDefault="True"
                    Command="{Binding GeocodeAddressCommand}"
                    CommandParameter="{Binding Text,ElementName=_txtAddress}"/>
        </StackPanel>

Notice that we are using the text of the TextBox as the CommandParameter.  Next, we need to implement the command:

private void GeocodeAddress(string address)
{
    using (BingMapsService.GeocodeServiceClient client = new BingMapsService.GeocodeServiceClient("CustomBinding_IGeocodeService"))
    {
        client.GeocodeCompleted += (o, e) =>
        {
            if (e.Error == null)
            {
                GeocodeResult = e.Result.Results[0];
            }
        };

        BingMapsService.GeocodeRequest request = new BingMapsService.GeocodeRequest();
        request.Credentials = new Credentials() { ApplicationId = (App.Current.Resources["MyCredentials"] as ApplicationIdCredentialsProvider).ApplicationId };
        request.Query = address;
        client.GeocodeAsync(request);
    }
}

In this command we are simply creating an instance of the service client, creating a GeocodeRequest, setting the credentials using the ApplicationId we have stored in the App.xaml, and then using the address as the Query for the request.  When the call to the service is completed we are setting the GeocodeResult property to the first element in the results.

Now the tricky part.  We want to place a Pushpin on the address that was entered and then move the map to that location.  We also need to make sure that when another address is entered, we clear any previous Pushpins that may exist on the map.  We also have to be aware that other elements my be on the map and we don’t want to accidentally alter those elements.  Oh and did I mention this was an MVVM application and this has to be done with just data binding?

The approach that I decided to go with is to use Attached Properties.  So I created two attached properties; one for the GeocodeResult and another for a MapLayer that will be dedicated to the GeocodeResult.  This will allow us to create a MapLayer that is specific to our GeocodeResut and not alter any other elements that may be on the map.  Let’s check out the code.

public class MapInteractivity
{
    #region GeocodeResult

    public static readonly DependencyProperty GeocodeResultProperty = DependencyProperty.RegisterAttached("GeocodeResult", typeof(BingMapsService.GeocodeResult), typeof(MapInteractivity), new UIPropertyMetadata(null, OnGeocodeResultChanged));
    public static BingMapsService.GeocodeResult GetGeocodeResult(Map target)
    {
        return (BingMapsService.GeocodeResult)target.GetValue(GeocodeResultProperty);
    }
    public static void SetGeocodeResult(Map target, BingMapsService.GeocodeResult value)
    {
        target.SetValue(GeocodeResultProperty, value);
    }
    private static void OnGeocodeResultChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        OnGeocodeResultChanged((Map)o, (BingMapsService.GeocodeResult)e.OldValue, (BingMapsService.GeocodeResult)e.NewValue);
    }
    private static void OnGeocodeResultChanged(Map map, BingMapsService.GeocodeResult oldValue, BingMapsService.GeocodeResult newValue)
    {
        Location location = newValue.Locations.Select(x => new Location(x.Latitude, x.Longitude)).First();

        Pushpin pin = new Pushpin();
        pin.Location = location;
        pin.ToolTip = newValue.Address.FormattedAddress;

        var locationLayer = GetGeocodeResultLayer(map);
        if (locationLayer == null)
        {
            locationLayer = new MapLayer();
            SetGeocodeResultLayer(map, locationLayer);
        }

        locationLayer.Children.Clear();
        locationLayer.Children.Add(pin);

        map.SetView(location, map.ZoomLevel);
    }

    #endregion //GeocodeResult

    #region GeocodeResultLayer

    public static readonly DependencyProperty GeocodeResultLayerProperty = DependencyProperty.RegisterAttached("GeocodeResultLayer", typeof(MapLayer), typeof(MapInteractivity), new UIPropertyMetadata(null, OnGeocodeResultLayerChanged));
    public static MapLayer GetGeocodeResultLayer(DependencyObject target)
    {
        return (MapLayer)target.GetValue(GeocodeResultLayerProperty);
    }
    public static void SetGeocodeResultLayer(DependencyObject target, MapLayer value)
    {
        target.SetValue(GeocodeResultLayerProperty, value);
    }
    private static void OnGeocodeResultLayerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        OnGeocodeResultLayerChanged((Map)o, (MapLayer)e.OldValue, (MapLayer)e.NewValue);
    }
    private static void OnGeocodeResultLayerChanged(Map map, MapLayer oldValue, MapLayer newValue)
    {
        map.Children.Add(newValue);
    }

    #endregion //GeocodeResultLayer
}

Notice that when the GeocodeResult changes we get the Location and create a pushpin for that location.  We also set the tooltip to the FormattedAddress of the GeocodeResult.  So when we hover over the pushpin we will see a nice little tooltip of the address.  Next we try to get the GeocodeResultLayer from the map, if it is null this means it doesn’t exist on the map yet so we create one and assign it accordingly.  We clear the layer child elemnts and then add out pushpin.  The last thing we do is call the SetView method of the Map control passing in the location and the current ZoomLevel.

The final step is to use our AttachedProperty on the map.  Be sure to create a namespace for the MapInteractivity class and define it as follows:

<bing:Map Grid.Row="1" CredentialsProvider="{StaticResource MyCredentials}" Center="40,-95"
          core:MapInteractivity.GeocodeResult="{Binding GeocodeResult}"
          ZoomLevel="4" AnimationLevel="Full" >
</bing:Map>

One last thing.  Be sure to remember to set your DataContext of your View to the ViewModel we have created:

public MainWindow()
{
    InitializeComponent();
    DataContext = new GeocodeViewModel();
}

That’s it.  You now have an application the can map (geocode) an address using the Bing Maps WPF Control and the Geocoding SOAP Service.  Oh and we are doing this all with MVVM and data binding which is my favorite part.  Be sure to Download the Source and start playing around.

Build an Earthquake Application with Bing Maps WPF Control (Beta)

Recently you may have noticed that the Bing Maps team released a WPF Map control which is currently in Beta.  In light of the recent earth quake on the east coast of the United States, I thought an earthquake application would be a great way to become familiar with the Map control.  There are a few things you must do before we can get started.

  1. Get the Bing Maps WPF control.
  2. Get a Bing Maps API key. (not needed for development)

Now that you have that out of the way let’s write an application.  Create a new WPF application targeting the .NET 4.0 framework.  Add a reference to the Microsoft.Maps.MapControl.WPF.dll.  This will most likely be located in Program Files or Program Files (x86) –> Bing Maps WPF Control –> Beta –> Libraries.  Next you need to declare a namespace in XAML:

xmlns:map="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"

Now just add the control to your view:
 
<Grid>
    <map:Map />
</Grid>

 

And this is the control at run time:

 
image_thumb1

Not much to look at, but it’s definitely a map. Notice that because we are not using an API key we get a nice little message that tells us where to get one.  Well this is great and all, but we want to see earth quakes!  So let’s start doing the real coding now. We know we will need an object to hold information about the earthquakes.

public class Earthquake
{
    public string Title { get; set; }
    public string Description { get; set; }
    public Location Location { get; set; }
}

That should take care of that.  Next we need data about earthquakes and their locations.  Luckily for us there is a free resource available to grab this information from USGS.  So let’s write a simple service to get this information:

public class EarthquakeService
{
    public static void GetRecentEarthquakes(EventHandler<EarthquakeEventArgs> callback)
    {
        WebClient client = new WebClient();
        client.OpenReadCompleted += (o, e) =>
        {
            XDocument doc = XDocument.Load(e.Result);

            var data = (from eq in doc.Element("rss").Element("channel").Elements("item")
                        select new Earthquake()
                        {
                            Title = eq.Element("title").Value,
                            Description = eq.Element("description").Value,
                            Location = new Location(Convert.ToDouble(eq.Element(XName.Get("lat", "http://www.w3.org/2003/01/geo/wgs84_pos#")).Value), Convert.ToDouble(eq.Element(XName.Get("long", "http://www.w3.org/2003/01/geo/wgs84_pos#")).Value))
                        }).ToList();

            callback(null, new EarthquakeEventArgs(data));
        };
        client.OpenReadAsync(new Uri("http://earthquake.usgs.gov/eqcenter/recenteqsww/catalogs/eqs7day-M2.5.xml"));
    }
}

public class EarthquakeEventArgs : EventArgs
{
    public List<Earthquake> Locations { get; set; }

    public EarthquakeEventArgs(List<Earthquake> locations)
    {
        Locations = locations;
    }
}

The last thing we need is a ViewModel that will expose our earthquakes from consumption by our view:

public class EarthquakeViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Earthquake> _earthquakes;
    public ObservableCollection<Earthquake> Earthquakes
    {
        get { return _earthquakes; }
        set
        {
            _earthquakes = value;
            OnPropertyChanged("Earthquakes");
        }
    }        

    public EarthquakeViewModel()
    {
        EarthquakeService.GetRecentEarthquakes((o, ea) =>
        {
            Earthquakes = new ObservableCollection<Earthquake>(ea.Locations);
        });
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Don’t forget to set the DataContext of the view to our ViewModel:

public MainWindow()
{
    InitializeComponent();
    DataContext = new EarthquakeViewModel();
}

 
Now we need to data bind our Earthquakes property to the Map control and to do that we need to use a MapItemsControl:
 
<map:Map>
    <map:MapItemsControl ItemsSource="{Binding Earthquakes}" />
</map:Map>

 

Here is what you will see when you run the application:

image_thumb3

HEY! Where the hell are my earthquakes?  This thing sucks!  Piece of sh….. Oh wait, I need to tell the map how to render the earthquakes.  So we need to create a DataTemplate:

<Window.Resources>
    <DataTemplate x:Key="EarthquakeTemplate">
        <map:Pushpin map:MapLayer.Position="{Binding Location}" />
    </DataTemplate>
</Window.Resources>

And don’t forget to specify the ItemTemplate:

<map:Map>
    <map:MapItemsControl ItemsSource="{Binding Earthquakes}"
                        ItemTemplate="{StaticResource EarthquakeTemplate}"/>
</map:Map>

 

Now let’s see what we get when we run the application:

image_thumb5

image_thumb7

Hey look! Earthquakes.  That is so cool. But wait, I want to see information about the earth quake.  Preferably in a popup when I hover over a Pushpin.  Well Lets wire that up real quick.  First we need to add a MapLayer to the map that will act as our popup:

<map:Map>
    <map:MapItemsControl ItemsSource="{Binding Earthquakes}"
                        ItemTemplate="{StaticResource EarthquakeTemplate}"/>

    <map:MapLayer x:Name="ContentPopupLayer">
        <Grid x:Name="ContentPopup" Visibility="Collapsed" Background="White" Opacity="0.85">
            <StackPanel Margin="15">
                <TextBlock x:Name="ContentPopupText" FontSize="12" FontWeight="Bold" ></TextBlock>
                <TextBlock x:Name="ContentPopupDescription" FontSize="12"></TextBlock>
            </StackPanel>
        </Grid>
    </map:MapLayer>
</map:Map>

Now we need to add some event handler to the ItemTemplate.

<DataTemplate x:Key="EarthquakeTemplate">
    <map:Pushpin map:MapLayer.Position="{Binding Location}" Tag="{Binding}" MouseEnter="Pushpin_MouseEnter" MouseLeave="Pushpin_MouseLeave" />
</DataTemplate>

We have added two event handlers; one for when we mouse over and the other for when the mouse is no longer over the pushpin.  Notice that I am storing the bound item in the Tag of the Pushpin object.  We will need that to change the location of popup.

private void Pushpin_MouseEnter(object sender, MouseEventArgs e)
{
    FrameworkElement pin = sender as FrameworkElement;
    MapLayer.SetPosition(ContentPopup, MapLayer.GetPosition(pin));
    MapLayer.SetPositionOffset(ContentPopup, new Point(20, -15));

    var location = (Earthquake)pin.Tag;

    ContentPopupText.Text = location.Title;
    ContentPopupDescription.Text = location.Description;
    ContentPopup.Visibility = Visibility.Visible;
}

private void Pushpin_MouseLeave(object sender, MouseEventArgs e)
{
    ContentPopup.Visibility = Visibility.Collapsed;
}

 

Now when we hover over the Pushpin we get some useful information about the earthquake:

image_thumb9

That’s it for this application.  The Bing Maps WPF Control has been a long awaited control and I am glad it finally made it to WPF.  As you can see it is pretty easy to use as well.  Now you can Download the source and start playing.