Introduction
Earth Quake is a composite WPF application designed to display all recently reported earthquakes from around the world using Bing Maps.
The application is targeted at the .NET 4 runtime with Prism 4 as its composite framework. The code examples and solution for Earth Quake
are in VB.NET and the solution is for Visual Studio 2010.
The article assumes the reader has some knowledge of WPF and that may be aware of Prism 4, MEF, and MVVM. This is not an article on MEF or MVVM, however there are some brief
explanations to help cover the topics.
Earth Quake has been created help illustrate the following:
- Prism composite application development
- Regions
- View discovery
- Communication through Event Aggregation and Commanding
- MVVM
- MEF
- Bing Maps
- WPF and data binding
Background
For me, WPF, Prism, MEF and MVVM are just pure pleasure to work with.
There are many fantastic articles out there that describe in great detail how to develop
applications using these technologies and patterns. I do not want to detract from
these so for that reason, see the links below to some resources that should help
if you are unfamiliar with them.
I wanted to design a small and basic application that covered some features
of Prism and WPF and hope that you will find it helpful or even useful. This article is one of a couple that I hope to bring to the table.
It is also worth noting (and something I thought I added but did not) was that whilst developing this application I found a very similar application that was already developed with the same concept. This application was a massive help to me and was developed by Brian Lagunas. Originally I was using a static image with dynamically placed points however Bing Maps did provide a better alternative. The example is found here!
Table of Contents
References
Earth Quake has a couple of referenced libraries that have been included with
the project download. The references are needed in order for you to run the
application.
Name |
Path |
Assemblies |
Description |
Prism 4 |
\EarthQuake\Libs\Prism 4\ |
Microsoft.Practices.Prism.dll
Microsoft.Practices.Prism.Interactivity.dll
Microsoft.Practices.Prism.MefExtensions.dll |
Prism 4 composite framework |
Bing Maps For WPF |
\EarthQuake\Libs\Bing Maps\ |
Microsoft.Maps.MapControl.WPF.dll |
Bing Maps WPF control |
Json.Net |
\EarthQuake\Libs\Json.Net\ |
Newtonsoft.Json.dll |
JSON framework for .NET |
In order to use MEF you will need to import the following system dll (MEF is part of the
.NET 4 runtime):
System.ComponentModel.Composition
The Maps module contains references to the Microsoft.Expression.Interactions
DLL. This is for executing commands on non-commanding objects. If you have expression studio 4 (Blend)
you will be ok. If not then I would recommend either installing Blend or downloading the Blend SDK from
here.:
Microsoft.Expression.Interactions
Bing Maps
Earth Quake uses the WPF control from the Bing Maps team that has recently
been released. Bing Maps had an API for AJAX etc but now there is a WPF control.
The control assembly is provided with the solution download however you many
want to get the SDK from
here.
Bing Maps API Key
Although not necessary to develop with, a Bing Maps API key should be
created from the
Bing Maps Portal. If not you will see a box over the map stating you should
get a key as shown below:
The portal also has links to some resources as well as the other SDK
downloads.
Once you have generated / registered a key you will need this later as
the Bing Maps control will require the key credential in order to remove the
message.
Json.Net
Earth Quake downloads the reported quake events from the
United States Geological Survey website. This
site has a host of feeds which vary in download format (CSV, ATOM, etc.) and
frequency of the registered earthquake. Earth Quake downloads a 30 day feed in
the Json format. For that reason I wanted to see if there was a .NET framework for JSON
that I could execute lambda expressions on.
Json.Net (by James Newton-King) is one such framework. It provides a
mechanism to query a JSON feed with ease.
Json.Net provides these features (taken from the website):
- Flexible JSON serializer for converting between .NET objects and
JSON
- LINQ to JSON for manually reading and writing JSON
- High performance, faster than .NET's built-in JSON serializers
- Write indented, easy to read JSON
- Convert JSON to and from XML
- Supports .NET 2, .NET 3.5, .NET 4, Silverlight, and Windows Phone
A brief example of how to use the framework is shown below:
Dim feed As String = "{""Name"": ""Apple"",""Expiry"": 1230422400000,""Price"": 3.99,
""Sizes"": [""Small"",""Medium"",""Large""]}"
Dim o As JObject = JObject.Parse(feed)
Dim name As String = DirectCast(o("Name"), String)
Dim sizes As JArray = DirectCast(o("Sizes"), JArray)
Dim smallest As String = DirectCast(sizes(0), String)
Prism 4
If you have been developing on Silverlight, WPF, or even WP7 for some time you may have encountered the phrase "Composite Application". Prism (Formally Composite Application Guidance for WPF and Silverlight), from the Microsoft Patterns and
Practices team
was created to allow developers to easily create modular and decoupled applications.
Using design patterns that embody important architectural design principles, such as separation of concerns and loose coupling, Prism helps you to design and build applications using loosely
coupled components that can evolve independently but which can be easily and seamlessly integrated into the overall application.
Earth Quake uses the Prism 4 framework to give you an example of
modularization. To download the framework and for more information please see the links below.
Earthquakes
As mentioned above, Earth Quake obtains reported quake information from the
United States Geological Survey
website. Each feed is broken down by the number of days since the event
happened, the strength of the quake and the format the information can be read
from.
The formats available are:
- CSV
- ATOM
- RSS
- Contains limited information compared to ATOM and JSON.
- JSON
- JSON(P)
- The same format as JSON however this data feed is wrapped inside a
function call,
eqfeed_callback
. Earth Quake uses the standard Json
feed.
- KML
- Google earths xml format for geographic apps.
- Twitter
- Email
Architecture
Solution
Earth Quake is a basic composite application. The solution is broken down into several different projects all providing unique functionality to the overall application.
The layout of the solution is shown below:
- Business. The shared project under the business
solution folder is for the majority of all shared business logic and domain
classes. These objects are unlikely to be shared between all modules as not
all modules will require it. The majority of Prism applications have the
Infrastructure project as their globally shared project however I like to
keep things a little lighter as this could become a very heavy project in
its own right. It is more likely that you would have shared (core) projects
for each module in a larger application.
- Core. These core projects are likely to be
referenced by the majority of your modules (if not all of them).
- Infrastructure: The infrastructure project has elements such as
composite commands, event payloads and interfaces. This project is very
important and will likely be referenced by all modules.
- Resources: Slightly less important compared with the infrastructure
project however, this project houses the elements such as themes, image
resources and other "application specific" styling.
- Modules. The modules are the projects that make up the
total functionality of the overall application. Each module can have a
direct dependency to the infrastructure project (and likely will). Modules
may not be part of the overall application solution as they could be
developed offsite for example. The beauty of having applications developed in a modular way is having the
opportunity
to focus development teams on individual aspects of the application without stepping on toes.
- Shell. This is the shell or module host that is used to
house and render views and implement logic from modules and other
dependencies. The shell could be a WPF or Silverlight application for
example.
MVVM
Source: Microsoft
Earth Quake uses the MVVM pattern throughout. There are many great MVVM
frameworks out there (MVVM light,
Cinch,
Jounce to name but a few) but I wanted
to keep the demo simple enough so that it just covers a couple of topics. Only
elements from the Prism framework and
Josh Smith's
ViewModelBase class are used. Again
this is not a tutorial on MVVM as I think this has been covered in better detail
by others.
MEF
Earth Quake uses MEF (Managed Extensibility Framework) to resolve views and viewmodels.
Each view will use property injection to wire up the views dependency on it's
related viewmodel. Earth Quake also uses constructor injection in other parts of the application.
Basically (and I mean basically) each view and viewmodel have an export class
attribute. With this
attribute MEF uses this to help resolve the object. If you have a property (or
constructor) where you want the object to be placed you would simply declare an
import attribute. This is shown below:
The Shell viewmodel. Notice the "Export" attribute.
<Export(GetType(ShellViewModel))> _
<PartCreationPolicy(NonShared)> _
Public Class ShellViewModel
Inherits
ViewModelBase
.....
End Class
The shell view will get the viewmodel via property injection. The "Exported"
viewmodel will be "Imported" into the shell. Notice the "Import
" attribute on
the ViewModel
property. This is shown below:
so it is injected by MEF with
<Import()> _
Private WriteOnly Property ViewModel() As
ShellViewModel
Set(ByVal value As ShellViewModel)
Me.DataContext = value
End Set
End Property
For those that are unfamiliar with MEF, MEF is a Microsoft framework for
resolving objects. MEF is sometimes referred to as a DI / IoC container however
this is not really what its purpose is for but does have many similarities.
Prism also supports the Unity framework which is the DI / IoC framework from
Microsoft however this is not covered in this article.
Earth Quake
OK, now the application. Earth Quake, in its current form, is a simple user
interface with one main view; the map. Other aspects of the UI relate to control
of the actual map and the status of the application.
UI Overview
The application is broken down into three regions:
- R1: The toolbar region.
- R2: The main map region.
- R3: The status bar region.
The application also has 3 main functions:
- Navigation - Zoom, centre and position.
- Tools - Change map style.
- Details - The earthquake information triggered via a pin mouse over.
The basic concept is that you are free to pan and move around the globe and
view any earthquake points that maybe on screen. Every time the user moves the
mouse over a map pin, the earthquake information is displayed in the Quake
details window.
Shell
The shell is the core application that can host the modules associated with
the application. The shell can be a Silverlight or WPF application. In this
instance the Shell project is a WPF .NET 4 application.
At present there is only one view and it's related ViewModel. This is an
important view as it is the core layout (similar to a masterpage in ASP.NET)
view containing regions that module views would be injected into. For a view /
shell to contain regions it is recommended that the following namespaces be
imported:
xmlns:inf="clr-namespace:EarthQuake.Infrastructure;assembly=EarthQuake.Infrastructure"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"
The "prism" namespace is the reference to the Microsoft.Practicies.Prism
library. This assembly is responsible for all region management. The "inf"
namespace is commonly a reference to the solution infrastructure project.
Although not mandatory, the reason for the reference is to use strongly typed
region and module names from the "WellKnownModuleNames
" and
"WellKnownRegionNames
" found in the infrastructure project.
The shell view (called shell.xaml) is initiated via the bootstrapper. The
shell view contains the following regions which will be injected into:
<!---->
<ItemsControl Grid.Row="1" prism:RegionManager.RegionName="{x:Static inf:WellKnownRegionNames.ToolBarRegion}" />
<!---->
<ContentControl Grid.Row="2" prism:RegionManager.RegionName="{x:Static inf:WellKnownRegionNames.MapRegion}" />
<!---->
<ContentControl Grid.Row="3" prism:RegionManager.RegionName="{x:Static inf:WellKnownRegionNames.StatusRegion}" />
There are two types of region containers used here. The ContentControl
and
the ItemsControl
. Basically the ContentControl
is used for displaying a single
view or UI element. The ItemsControl
is for multiple items such as buttons. You
will notice that the RegionManager.RegionName
property is set to the strongly
typed name from the WellKnownModuleNames
as explained above.
Bootstrapper
The other aspect of the shell is to provide a means to specify the modules
using a module catalogue and to configure the region adapters or behaviors.
Earth Quake inherits from the MEF Bootstrapper which comes from the
Microsoft.Practices.Prism.MefExtensions assembly. The Bootstrapper creates
and initializes the Shell window and configures the modules associated with the
project. Note that the bootstrapper class should be at the root level of the
project.
The ConfigureAggregateCatalog()
from the Bootstrapper is the method
responsible for creating references to the modules. Currently all modules are
referenced in code but you could have a module directory to monitor or xml
configuration file to use. More common than not is to reference the modules in
code however I find that the directory method does provide a more "loosely
coupled" scenario.
Protected Overrides Sub ConfigureAggregateCatalog()
MyBase.ConfigureAggregateCatalog()
AggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(Bootstrapper).Assembly))
AggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(StatusModule.StatusModule).Assembly))
AggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(MapModule.MapModule).Assembly))
AggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(ToolBarModule.ToolBarModule).Assembly))
End Sub
In order for the application to initialize the bootstrapper and make the
composite application come alive, the bootstrapper needs to be instantiate in
the OnStartup
event. This normally done in the Application.xaml code behind.
Protected Overrides Sub OnStartup(ByVal e As StartupEventArgs)
MyBase.OnStartup(e)
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf AppDomainUnhandledException
Me.ShutdownMode = ShutdownMode.OnMainWindowClose
Try
Dim bootstrapper As New Bootstrapper()
bootstrapper.Run()
Catch ex As Exception
HandleException(ex)
End Try
End Sub
Infrastructure
The Infrastructure is the core application assembly. This should and will
contain all core functionality to be shared across each module. This
functionality will be things like interfaces, event payloads, composite global
commands and other key objects.
Earth Quake does use an infrastructure project to contain these important
aspects of functionality however there is another slightly less important shared
library used to house all domain objects that are less important to the overall
application as not all modules will reference it. This is to keep things a
little lighter as the infrastructure project could in its own right become too
heavy. You will more likely in larger application, see modules with individual
core / shared assemblies that house module dependent objects but this is not the
case with Earth Quake.
Modules
There are three modules that exist as part of the application which are found
in the solution folder "Modules". Modules can be
part of the overall solution or from satellite assemblies either developed
offsite or from a third party (being an extensible add-in). In order for a module
to exist there must be a class that implements the IModule
(member of
Microsoft.Practices.Prism.Modularity) interface. This class will be
responsible for registering the module views with the application regions.
Each module initiation class (a class that implements the IModule
interface)
has a method called Initialize
that is called when the module is
instantiated in
the application bootstrapper. For each module in Earth Quake, the views within
them are registered with a region via the region manager. To register a view
using a term called view discovery, the method RegisterViewWithRegion
is used.
View discovery is a pattern that will ensure the view is added to a region when
the region is added automatically. View Injection on the other
hand is where a view is added (Injected) into a region which is generally done
manually and does require the region to be present for obvious reasons.
The two examples below show how this could be achieved.
_regionManager.RegisterViewWithRegion(WellKnownRegionNames.MapRegion, GetType(WorldMap))
_regionManager.Regions(WellKnownRegionNames.MapRegion).Add(GetType(Map))
Toolbar
The toolbar module is pretty simple. It is a basic primitive toolbar control
with two buttons on it; Refresh and Exit. These buttons use two different
mechanisms to achieve an event. Refresh uses the event aggregator to raise an
event to event subscribers which could be in multiple modules. The Exit button
uses a composite command to trigger any subscribed delegate commands. In this
instance, the exit button triggers the ExitApplicationCommand
(from the
EarthQuake.Infrastructure.Commands.Global
namespace). But how does it
actually exit the application?. Buried in the shell ViewModel is a delegate
command that has been registered to the global composite command ExitApplicationCommand
.
Any delegate commands that are subscribed to the global composite command will
receive the event notification and will action any delegate method associated
with that command.
Status Bar
The status bar is again a fairly simple statusbar control. The status bar
relies heavily on event aggregation for displaying the map centre location
stating the latitude and longitude. The status bar also gets a status message in
the same way.
Map
The map module is the main module and does the majority of the work. The
module contains a view (map.xaml), a viewmodel (MapViewModel), and a model
(MapModel).
Setting Bing Maps
To setup Bing Maps you need to reference the Microsoft.Maps.MapControl.WPF.dll.
You then need to add the reference in the view.
xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
Because the Map pins use the Microsoft.Expression.Interactions
assembly for triggering commands via mouse enter and mouse leave, you will need
to reference this assembly in your project and then reference it in the markup.
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Once you have a reference to the map control, you can then add the control to the markup.
<m:Map Name="WorldMap"
AnimationLevel="Full"
Center="{Binding MapCentreLocation, Mode=TwoWay}"
CredentialsProvider="Your map API Key here"
Mode="{Binding MapMode}"
ZoomLevel="{Binding ElementName=Zoom, Path=Value, Mode=TwoWay}">
<m:MapItemsControl ItemsSource="{Binding EarthquakeLocations}"
ItemTemplate="{StaticResource EarthquakeLocationsTemplate}" />
</m:Map>
The map has a few properties on it which are bound to the viewmodel or bound
to a few other controls on the page. Note that the property
"CredentialsProvider
" should be where the API key is added. Again for
development purposes this is not required however I would get one.
In order to display map pins you will need to set the "MapItemsControl
". You
will notice that the MapItemsControl
has been bound to the EarthquakeLocations
ObservableCollection
property in the viewmodel. I have set an ItemTemplate
for
the pins as shown here:
<DataTemplate x:Key="EarthquakeLocationsTemplate">
<m:Pushpin Name="Pin"
m:MapLayer.Position="{Binding Location}"
Tag="{Binding}"
ToolTip="{Binding Title}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding ElementName=WorldMapView, Path=DataContext.PinMouseOverCommand}" CommandParameter="{Binding}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding ElementName=WorldMapView, Path=DataContext.PinMouseOverCommand}" CommandParameter="" />
</i:EventTrigger>
</i:Interaction.Triggers>
</m:Pushpin>
</DataTemplate>
You will notice the alias "i
" used for the event triggers. This alias is to
the Expression interactivity namespace. In order to pop open the Earthquake
details WPF popup control I used the interactions to trigger and invoke the
delegate command "PinMouseOverCommand
" in the viewmodel. Basically this just
opens and closes the details window depending on your mouse position.
Getting the Map Data
As mentioned at the start of the article, the earthquake data comes from the
USGS data feed in the JSON format. The Json.Net framework is used to help parse
and query the feed with ease. The map module contains an Earthquake repository
USGSRepository
. The following shows how the list of earthquakes are
populated and returned to the viewmodel for binding.
Public Function FindAll() As List(Of [Shared].Earthquake) _
Implements Infrastructure.IEarthquakeRepository.FindAll
Dim feed As String
Dim feedObject As JObject
Try
Using client As New WebClient
client.Headers.Add("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)")
feed = client.DownloadString(New Uri("http://earthquake.usgs.gov/earthquakes/feed/geojson/2.5/month"))
feedObject = JObject.Parse(feed)
End Using
Return (From item In feedObject.SelectToken("features")
Select New [Shared].Earthquake(item.SelectToken("id").ToString) With _
{.Title = item.SelectToken("properties.place").ToString,
.Magnitude = item.SelectToken("properties.mag").ToString,
.Latitude = item.SelectToken("geometry.coordinates[1]"),
.Longitude = item.SelectToken("geometry.coordinates[0]"),
.LinkURL = String.Concat("http://earthquake.usgs.gov", item.SelectToken("properties.url").ToString),
.EventDateTime = UTCDateTimeHelper.UTCTimeConverter(item.SelectToken("properties.time").ToString),
.Source = "USGS"}).ToList
Catch ex As Exception
Return New List(Of [Shared].Earthquake)
End Try
End Function
So lastly the viewmodel. The viewmodel has a basic method that is called to
grab this data and populate the observable collection so that the map pins can
be positioned at the latitude and longitude co-ordinates.
Private Sub SetEarthquakeLocations()
_eventAggregator.GetEvent(Of Infrastructure.Events.Composite.StatusUpdatedEvent).Publish("Updating Quake locations...")
task.Factory.StartNew(Sub()
Dim mapModel As New MapModel(New USGSRepository)
For Each quake In mapModel.GetEarthquakeData
If Not EarthquakeLocations.Contains(quake) Then
Application.Current.Dispatcher.BeginInvoke(Sub(q)
EarthquakeLocations.Add(q)
End Sub, {quake})
End If
Next
End Sub).ContinueWith(Sub(antecedent)
Dim message As String
If antecedent.IsFaulted Then
message = "Error getting quake events"
Else
message = "Idle..."
End If
_eventAggregator.GetEvent(Of Infrastructure.Events.Composite.StatusUpdatedEvent).Publish(message)
End Sub)
End Sub
You will notice there is an event aggregator publishing a status message. The
status bar subscribes to this event to display the message.
Conclusion
This is just the initial building blocks to a Bing Maps application using
WPF. The Bing Maps control is great if you need to plot events or locations on
the world map. In the next article I want to provide a means to show real-time
events and how this can be used in the application. I hope this article is of
some use even though many of the topics such as MEF and MVVM are not discussed
in any detail.
History
- 23/05/2012 - First version.