bobby's blog

randomly specific...


News

My Stats

  • Posts - 15
  • Comments - 49
  • Trackbacks - 0

Twitter












Recent Comments


Recent Posts


Archives


Post Categories


Image Galleries


My Links


User Groups


 

A popular topic that comes up when talking about MVVM is the use of a ViewModelLocator and the many different ways one can be implemented.  Rather than getting into the pros and cons on when or why you should use it, I decided I would just post my version of a simple ViewModelLocator and let those who like it use it, and those who don’t, well you know…  :)

First, a disclaimer.  I have not used this code in a production application, it is just something I was tossing around while reading others’ posts on the subject.

1. MainView.xaml

image

 

2. MainViewModel.cs

image


3. ViewModelLocator.cs

image

 

I have a codepaste of the ViewModelLocator.cs file if you are interested but don’t feel like re-typing the 50 lines of code!

Enjoy!

Additional Resources

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

Well, day 2 of the MIX10 conference did not disappoint.  The keynote speakers introduced the preview release of IE9, which looks really cool and quick, and Visual Studio 2010 RC that is scheduled to RTM on April 12th.  It seemed to have a lot of improvements aimed at making developers more productive.  Here are the current links to these two offerings:


While both of these were interesting, the demos that really blew me away today centered around the work being done with The Open Data Protocol, or OData for short!  OData is a recommended standard being pushed by Microsoft that uses a REST based interface to interact with various types of data in a uniform manner.  Data producers then provide the data to consumer in either ATOM or JSON formats as requested by the client application.


The OData SDK contains client and server libraries for many of the popular languages in use today, including .NET, Java, PHP, Objective C and JavaScript, so you consume or even produce your own OData services.  More information can be found using the following links:


Netflix has made available one of the first live OData services by exposing their entire movie catalog.  You can browse and query using URLs similar to the following:


So now I just need to find an excuse reason to start using OData in a real project!


Enjoy!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

There were a lot of announcements made during the keynote at MIX10 today, most notable were the releases of Silverlight 4 RC, Silverlight 4 Tools for Visual Studio 2010, Expression Blend 4 Beta and the Windows Phone 7 Developer Tools.  I was glad to see that developers will be able to use Silverlight to create awesome applications for Windows Phone 7 so we can reuse our WPF and Silverlight skills to target mobile devices!

With so much information coming out of this conference, I wanted to be sure to save a list of links that I can quickly reference as I learn about these exciting new technologies:

Silverlight 4

Windows Phone 7


Whew, and that’s just from day 1!  Can’t wait to see what else comes out tomorrow.  Hopefully these links will give you a good starting point for Silverlight 4 and Windows Phone 7 information.


Enjoy!
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

Just a quick update to let everyone know that I have updated the Whiteboard demo application.  I added a few options to make it more interesting to use!  I showed it to the kids and they loved it (even though they kept asking ME to draw pictures for them)!
 

Here is a list of available options:

  • Color Picker for line color
  • Slider for line thickness
  • Save to valid XAML file
  • Open saved drawing
  • Clear whiteboard
  • Hold down ESC key to erase


And here is a screenshot of my beautiful artwork! :)


So what are you waiting for?  Go play with the Live Demo (and be sure to share with the kids) or download the source code.


Enjoy!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

I have come across several instances of people having trouble using the new Reactive Extensions (v1.0.2.5) in projects that reference the Silverlight Toolkit (Nov09) due to the fact that the original release of the Rx Framework (v1.0.0.0) was bundled with the Toolkit.  The trouble really becomes evident if you are using the Managed Extensibility Framework (MEF) to discover and compose portions of your application.
 

Update:  After getting some feedback in the comments, I was able to find a solution for the type load exception that works along with the DragDrop class of the Silverlight Toolkit!  The key is to load both versions of the System.Reactive.dll into the application.  You can't do it via the standard Add Reference, but the following code illustrates how it can be done:

    public MainPage()

    {

        // force previous version to be loaded

        var part = new AssemblyPart() { Source = "References/System.Reactive.v1.0.0.0.dll" };

        Deployment.Current.Parts.Add(part);

        LoadReferences();

 

        InitializeComponent();

 

 

        // this works now!

        CompositionInitializer.SatisfyImports(this);

        References.ItemsSource = references;

    }

The updated source code includes the previous dll in the Silverlight project as Content.  End Update.


If you are using the CompositionInitializer, or any other mechanism that probes all of the loaded assemblies for valid exports, you will likely receive the following error:


Inspecting the LoaderExceptions property yields the following:
 

System.IO.FileNotFoundException: Could not load file or assembly 'System.Reactive, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1b331ac6720247d9' or one of its dependencies. The system cannot find the file specified.  File name: 'System.Reactive, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1b331ac6720247d9'


This is due to some of the Toolkit assemblies referencing the older System.Reactive.dll.  I was able to work around the issue by bypassing the automatic probing of loaded assemblies and instead specified which assemblies my exports could be found.

    public MainPage()

    {

        InitializeComponent();

 

        // the following line causes a ReflectionTypeLoadException

        //CompositionInitializer.SatisfyImports(this);

 

        // skip the toolkit assemblies by specifying assemblies

        var catalog = new AssemblyCatalog(GetType().Assembly);

        var container = new CompositionContainer(catalog);

        container.ComposeParts(this);

 

        ShowReferences();

    }


With some simple xaml, I was able to print out exactly which libraries are currently loaded in the application.


You can download the sample project to run it for yourself!


Hope that helps!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

Update (14-Mar-2010): Updated the Whiteboard Demo
 

I must have tried reading through the various explanations and introductions to the new Reactive Extensions for .NET before the concepts finally started sinking in.  The article that gave me the ah-ha moment was over on SilverlightShow.net and titled Using Reactive Extensions in Silverlight.  The author did a good job comparing the "normal" way of handling events vs. the new "reactive" methods.


Admittedly, I still have more to learn about the Rx Framework, but I wanted to put together a sample project so I could start playing with the new Observable and IObservable<T> constructs.  I decided to throw together a whiteboard application in Silverlight based on the Drawing with Rx example on the aforementioned article.  At the very least, I figured I would learn a thing or two about a new technology, but my real goal is to create a fun application that I can share with the kids since they love drawing and coloring so much!


Here is the code sample that I borrowed from the article:

var mouseMoveEvent = Observable.FromEvent<MouseEventArgs>(this, "MouseMove");

var mouseLeftButtonDown = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonDown");

var mouseLeftButtonUp = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonUp");

 

    var draggingEvents = from pos in mouseMoveEvent

                             .SkipUntil(mouseLeftButtonDown)

                             .TakeUntil(mouseLeftButtonUp)

                             .Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>

                                 new

                                 {

                                     X2 = cur.EventArgs.GetPosition(this).X,

                                     X1 = prev.EventArgs.GetPosition(this).X,

                                     Y2 = cur.EventArgs.GetPosition(this).Y,

                                     Y1 = prev.EventArgs.GetPosition(this).Y

                                 })).Repeat()

                         select pos;

 

    draggingEvents.Subscribe(p =>

    {

        Line line = new Line();

        line.Stroke = new SolidColorBrush(Colors.Black);

        line.StrokeEndLineCap = PenLineCap.Round;

        line.StrokeLineJoin = PenLineJoin.Round;

        line.StrokeThickness = 5;

        line.X1 = p.X1;

        line.Y1 = p.Y1;

        line.X2 = p.X2;

        line.Y2 = p.Y2;

        this.LayoutRoot.Children.Add(line);

    });


One thing that was nagging at the back of my mind was having to deal with the event names as strings, as well as the verbose syntax for the Observable.FromEvent<TEventArgs>() method.  I came up with a couple of static/helper classes to resolve both issues and also created a T4 template to auto-generate these helpers for any .NET type.  Take the following code from the above example:

var mouseMoveEvent = Observable.FromEvent<MouseEventArgs>(this, "MouseMove");

var mouseLeftButtonDown = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonDown");

var mouseLeftButtonUp = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonUp");


Turns into this with the new static Events class:

var mouseMoveEvent = Events.Mouse.Move.On(this);

var mouseLeftButtonDown = Events.Mouse.LeftButtonDown.On(this);

var mouseLeftButtonUp = Events.Mouse.LeftButtonUp.On(this);


Or better yet, just remove the variable declarations altogether:

    var draggingEvents = from pos in Events.Mouse.Move.On(this)

                             .SkipUntil(Events.Mouse.LeftButtonDown.On(this))

                             .TakeUntil(Events.Mouse.LeftButtonUp.On(this))

                             .Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>

                                 new

                                 {

                                     X2 = cur.EventArgs.GetPosition(this).X,

                                     X1 = prev.EventArgs.GetPosition(this).X,

                                     Y2 = cur.EventArgs.GetPosition(this).Y,

                                     Y1 = prev.EventArgs.GetPosition(this).Y

                                 })).Repeat()

                         select pos;


The Move, LeftButtonDown and LeftButtonUp members of the Events.Mouse class are readonly instances of the ObservableEvent<TTarget, TEventArgs> class that provide type-safe access to the events via the On() method.  Here is the code for the class:

using System;

using System.Collections.Generic;

using System.Linq;

 

namespace System.Linq

{

    /// <summary>

    /// Represents an event that can be managed via the <see cref="Observable"/> API.

    /// </summary>

    /// <typeparam name="TTarget">The type of the target.</typeparam>

    /// <typeparam name="TEventArgs">The type of the event args.</typeparam>

    public class ObservableEvent<TTarget, TEventArgs> where TEventArgs : EventArgs

    {

        /// <summary>

        /// Initializes a new instance of the <see cref="ObservableEvent"/> class.

        /// </summary>

        /// <param name="eventName">Name of the event.</param>

        protected ObservableEvent(String eventName)

        {

            EventName = eventName;

        }

 

        /// <summary>

        /// Registers the specified event name.

        /// </summary>

        /// <param name="eventName">Name of the event.</param>

        /// <returns></returns>

        public static ObservableEvent<TTarget, TEventArgs> Register(String eventName)

        {

            return new ObservableEvent<TTarget, TEventArgs>(eventName);

        }

 

        /// <summary>

        /// Creates an enumerable sequence of event values for the specified target.

        /// </summary>

        /// <param name="target">The target.</param>

        /// <returns></returns>

        public IObservable<IEvent<TEventArgs>> On(TTarget target)

        {

            return Observable.FromEvent<TEventArgs>(target, EventName);

        }

 

        /// <summary>

        /// Gets or sets the name of the event.

        /// </summary>

        /// <value>The name of the event.</value>

        public string EventName { get; private set; }

    }

}


And this is how it's used:

    /// <summary>

    /// Categorizes <see cref="ObservableEvents"/> by class and/or functionality.

    /// </summary>

    public static partial class Events

    {

        /// <summary>

        /// Implements a set of predefined <see cref="ObservableEvent"/>s

        /// for the <see cref="System.Windows.System.Windows.UIElement"/> class

        /// that represent mouse related events.

        /// </summary>

        public static partial class Mouse

        {

            /// <summary>Represents the MouseMove event.</summary>

            public static readonly ObservableEvent<UIElement, MouseEventArgs> Move =

                ObservableEvent<UIElement, MouseEventArgs>.Register("MouseMove");

 

            // additional members omitted...

        }

    }


The source code contains a static Events class with prefedined members for various categories (Key, Mouse, etc.).  There is also an Events.tt template that you can customize to generate additional event categories for any .NET type.  All you should have to do is add the name of your class to the types collection near the top of the template:

    types = new Dictionary<String, Type>()

    {

        //{ "Microsoft.Maps.MapControl.Map, Microsoft.Maps.MapControl", null }

        { "System.Windows.FrameworkElement, System.Windows", null },

        { "Whiteboard.MainPage, Whiteboard", null }

    };


The template is also a bit rough at this point, but at least it generates code that *should* compile.  Please let me know if you run into any issues with it.  Some people have reported errors when trying to use T4 templates within a Silverlight project, but I was able to get it to work with a little black magic... 


You can download the source code for this project or play around with the live demo.  Just be warned that it is at a very early stage so don't expect to find much today.  I plan on adding alot more options like pen colors and sizes, saving, printing, etc. as time permits.  HINT: hold down the ESC key to erase!


Enjoy!


Additional Resources

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

Quick Links


Since volcanos are often associated with earthquakes, and vice versa, I decided to show recent volcanic activity on the Earthquake Locator map.  I am pulling the data from a website created for a joint project between the Smithsonian's Global Volcanism Program and the US Geological Survey's Volcano Hazards Program, found here.  They provide a Weekly Volcanic Activity Report as an RSS feed.
 

I started implementing this new functionality by creating a new Volcano entity in the domain model and adding the following to the EarthquakeService class (I also factored out the common reading/parsing helper methods to a separate FeedReader class that can be used by multiple domain service classes):
 

        private static readonly string VolcanoFeedUrl =

            ConfigurationManager.AppSettings["VolcanoFeedUrl"];

 

        /// <summary>

        /// Gets the volcano data for the previous week.

        /// </summary>

        /// <returns>A queryable collection of <see cref="Volcano"/> objects.</returns>

        public IQueryable<Volcano> GetVolcanos()

        {

            var feed = FeedReader.Load(VolcanoFeedUrl);

            var list = new List<Volcano>();

 

            if ( feed != null )

            {

                foreach ( var item in feed.Items )

                {

                    var quake = CreateVolcano(item);

                    if ( quake != null )

                    {

                        list.Add(quake);

                    }

                }

            }

 

            return list.AsQueryable();

        }

 

        /// <summary>

        /// Creates a <see cref="Volcano"/> object for each item in the RSS feed.

        /// </summary>

        /// <param name="item">The RSS item.</param>

        /// <returns></returns>

        private Volcano CreateVolcano(SyndicationItem item)

        {

            Volcano volcano = null;

            string title = item.Title.Text;

            string desc = item.Summary.Text;

            double? latitude = null;

            double? longitude = null;

 

            FeedReader.GetGeoRssPoint(item, out latitude, out longitude);

 

            if ( !String.IsNullOrEmpty(title) )

            {

                title = title.Substring(0, title.IndexOf('-'));

            }

            if ( !String.IsNullOrEmpty(desc) )

            {

                desc = String.Join("\n\n", desc

                        .Replace("<p>", "")

                        .Split(

                            new string[] { "</p>" },

                            StringSplitOptions.RemoveEmptyEntries)

                        .Select(s => s.Trim())

                        .ToArray())

                        .Trim();

            }

 

            if ( latitude != null && longitude != null )

            {

                volcano = new Volcano()

                {

                    Id = item.Id,

                    Title = title,

                    Description = desc,

                    Url = item.Links.Select(l => l.Uri.OriginalString).FirstOrDefault(),

                    Latitude = latitude.GetValueOrDefault(),

                    Longitude = longitude.GetValueOrDefault()

                };

            }

 

            return volcano;

        }


I then added the corresponding LoadVolcanos() method and Volcanos collection to the EarthquakeViewModel class in much the same way I did with the Earthquakes in my previous article in this series.

Now that I am starting to add more information to the map, I wanted to give the user some options as to what is displayed and allowing them to choose what gets turned off.  I have updated the MainPage.xaml to look like this:
 

<UserControl x:Class="EarthquakeLocator.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:basic="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"

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

    xmlns:vm="clr-namespace:EarthquakeLocator.ViewModel"

    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"

>

    <UserControl.Resources>

        <DataTemplate x:Key="EarthquakeTemplate">

            <Ellipse Fill="Red" Stroke="Black" StrokeThickness="1"

                     Width="{Binding Size}" Height="{Binding Size}"

                     bing:MapLayer.Position="{Binding Location}"

                     bing:MapLayer.PositionOrigin="Center">

                <ToolTipService.ToolTip>

                    <StackPanel>

                        <TextBlock Text="{Binding Title}" FontSize="14" FontWeight="Bold" />

                        <TextBlock Text="{Binding UtcTime}" />

                        <TextBlock Text="{Binding LocalTime}" />

                        <TextBlock Text="{Binding DepthDesc}" />

                    </StackPanel>

                </ToolTipService.ToolTip>

            </Ellipse>

        </DataTemplate>

 

        <DataTemplate x:Key="VolcanoTemplate">

            <Polygon Fill="Gold" Stroke="Black" StrokeThickness="1" Points="0,10 5,0 10,10"

                     bing:MapLayer.Position="{Binding Location}"

                     bing:MapLayer.PositionOrigin="Center"

                     MouseLeftButtonUp="Volcano_MouseLeftButtonUp">

                <ToolTipService.ToolTip>

                    <StackPanel>

                        <TextBlock Text="{Binding Title}" FontSize="14" FontWeight="Bold" />

                        <TextBlock Text="Click icon for more information..." />

                    </StackPanel>

                </ToolTipService.ToolTip>

            </Polygon>

        </DataTemplate>

    </UserControl.Resources>

 

    <UserControl.DataContext>

        <vm:EarthquakeViewModel AutoLoadData="True" />

    </UserControl.DataContext>

 

    <Grid x:Name="LayoutRoot">

 

        <bing:Map x:Name="map" CredentialsProvider="--Your-Bing-Maps-Key--"

                  Center="{Binding MapCenter, Mode=TwoWay}"

                  ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}">

 

            <bing:MapItemsControl ItemsSource="{Binding Earthquakes}"

                                  ItemTemplate="{StaticResource EarthquakeTemplate}" />

 

            <bing:MapItemsControl ItemsSource="{Binding Volcanos}"

                                  ItemTemplate="{StaticResource VolcanoTemplate}" />

        </bing:Map>

 

        <basic:TabControl x:Name="tabs" VerticalAlignment="Bottom" MaxHeight="25" Opacity="0.7">

            <basic:TabItem Margin="90,0,-90,0" MouseLeftButtonUp="TabItem_MouseLeftButtonUp">

                <basic:TabItem.Header>

                    <TextBlock x:Name="txtHeader" Text="Options"

                               FontSize="13" FontWeight="Bold" />

                </basic:TabItem.Header>

 

                <StackPanel Orientation="Horizontal">

                    <TextBlock Text="Earthquakes:" FontWeight="Bold" Margin="3" />

                    <StackPanel Margin="3">

                        <CheckBox Content=" &lt; 4.0"

                                  IsChecked="{Binding ShowLt4, Mode=TwoWay}" />

                        <CheckBox Content="4.0 - 4.9"

                                  IsChecked="{Binding Show4s, Mode=TwoWay}" />

                        <CheckBox Content="5.0 - 5.9"

                                  IsChecked="{Binding Show5s, Mode=TwoWay}" />

                    </StackPanel>

 

                    <StackPanel Margin="10,3,3,3">

                        <CheckBox Content="6.0 - 6.9"

                                  IsChecked="{Binding Show6s, Mode=TwoWay}" />

                        <CheckBox Content="7.0 - 7.9"

                                  IsChecked="{Binding Show7s, Mode=TwoWay}" />

                        <CheckBox Content="8.0 +"

                                  IsChecked="{Binding ShowGe8, Mode=TwoWay}" />

                    </StackPanel>

 

                    <TextBlock Text="Other:" FontWeight="Bold" Margin="50,3,3,3" />

                    <StackPanel Margin="3">

                        <CheckBox Content="Volcanos"

                                  IsChecked="{Binding ShowVolcanos, Mode=TwoWay}" />

                    </StackPanel>

                </StackPanel>

 

            </basic:TabItem>

        </basic:TabControl>

 

    </Grid>

</UserControl>


Notice that I added a VolcanoTemplate that uses a triangle-shaped Polygon to represent the Volcano locations, and I also added a second <bing:MapItemsControl /> tag to the map to bind to the Volcanos collection.  The TabControl found below the map houses the options panel that will present the user with several checkboxes so they can filter the different points based on type and other properties (i.e. Magnitude).  Initially, the TabItem is collapsed to reduce it's footprint, but the screen shot below shows the options panel expanded to reveal the available settings:
 

 

I have updated the Source Code and Live Demo to include these new features.
 

Happy Mapping!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

Quick Links


I finally got a live demo up and running!  I signed up for a shared hosting account over at discountasp.net so I could post a working version of the Earthquake Locator application, but ran into a few minor issues related to RIA Services.  Thankfully, Tim Heuer had already encountered and explained all of the problems I had along with solutions to these and other common pitfalls.  You can find his blog post here.  The ones that got me were the default authentication tag being set to Windows instead of Forms, needed to add the <baseAddressPrefixFilters> tag since I was running on a shared server using host headers, and finally the Multiple Authentication Schemes settings in the IIS7 Manager.
 

To get the demo application ready, I pulled down local copies of the earthquake data feeds that the application can use instead of pulling from the USGS web site.  I basically added the feed URL as an app setting in the web.config:
 

    <appSettings>

        <!-- USGS Data Feeds: http://earthquake.usgs.gov/earthquakes/catalogs/ -->

        <!--<add key="FeedUrl"

            value="http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml" />-->

        <!--<add key="FeedUrl"

            value="http://earthquake.usgs.gov/earthquakes/catalogs/7day-M2.5.xml" />-->

        <!--<add key="FeedUrl"

            value="~/Demo/1day-M2.5.xml" />-->

        <add key="FeedUrl"

             value="~/Demo/7day-M2.5.xml" />

    </appSettings>


You will need to do the same if you want to run from local copies of the feed data.  I also made the following minor changes to the EarthquakeService class so that it gets the FeedUrl from the web.config:
 

    private static readonly string FeedUrl = ConfigurationManager.AppSettings["FeedUrl"];

 

    /// <summary>

    /// Gets the feed at the specified URL.

    /// </summary>

    /// <param name="url">The URL.</param>

    /// <returns>A <see cref="SyndicationFeed"/> object.</returns>

    public static SyndicationFeed GetFeed(String url)

    {

        SyndicationFeed feed = null;

 

        if ( !String.IsNullOrEmpty(url) && url.StartsWith("~") )

        {

            // resolve virtual path to physical file system

            url = System.Web.HttpContext.Current.Server.MapPath(url);

        }

 

        try

        {

            log.Debug("Loading RSS feed: " + url);

 

            using ( var reader = XmlReader.Create(url) )

            {

                feed = SyndicationFeed.Load(reader);

            }

        }

        catch ( Exception ex )

        {

            log.Error("Error occurred while loading RSS feed: " + url, ex);

        }

 

        return feed;

    }


You can now view the live demo or download the source code here, but be sure you have WCF RIA Services installed before running the application locally and make sure the FeedUrl is pointing to a valid location.  Please let me know if you have any comments or if you run into any issues with the code.
 

Enjoy!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

Quick Links


The recent wave of earthquakes (no pun intended) being reported in the news got me wondering about the frequency and severity of earthquakes around the world. Since I’ve been doing a lot of Silverlight development lately, I decided to scratch my curiosity with a nice little Bing Maps application that will show the location and relative strength of recent seismic activity.

Here is a list of technologies this application will utilize, so be sure to have everything downloaded and installed if you plan on following along.

* If you are new to Bing Maps or have not signed up for a Developer Account, you will need to visit www.bingmapsportal.com to request a Bing Maps key for your application.
Getting Started
We start out by creating a new Silverlight Application called EarthquakeLocator and specify that we want to automatically create the Web Application Project with RIA Services enabled.
I cleaned up the web app by removing the Default.aspx and EarthquakeLocatorTestPage.html. Then I renamed the EarthquakeLocatorTestPage.aspx to Default.aspx and set it as my start page. I also set the development server to use a specific port, as shown below.
RIA Services
Next, I created a Services folder in the EarthquakeLocator.Web project and added a new Domain Service Class called EarthquakeService.cs. This is the RIA Services Domain Service that will provide earthquake data for our client application. I am not using LINQ to SQL or Entity Framework, so I will use the <empty domain service class> option. We will be pulling data from an external Atom feed, but this example could just as easily pull data from a database or another web service. This is an important distinction to point out because each scenario I just mentioned could potentially use a different Domain Service base class (i.e. LinqToSqlDomainService<TDataContext>).
Now we can start adding Query methods to our EarthquakeService that pull data from the USGS web site. Here is the complete code for our service class:

using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.ServiceModel.Syndication;

using System.Web.DomainServices;

using System.Web.Ria;

using System.Xml;

using log4net;

using EarthquakeLocator.Web.Model;

 

namespace EarthquakeLocator.Web.Services

{

    /// <summary>

    /// Provides earthquake data to client applications.

    /// </summary>

    [EnableClientAccess()]

    public class EarthquakeService : DomainService

    {

        private static readonly ILog log = LogManager.GetLogger(typeof(EarthquakeService));

 

        // USGS Data Feeds: http://earthquake.usgs.gov/earthquakes/catalogs/

        private const string FeedForPreviousDay =

            "http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml";

        private const string FeedForPreviousWeek =

            "http://earthquake.usgs.gov/earthquakes/catalogs/7day-M2.5.xml";

 

        /// <summary>

        /// Gets the earthquake data for the previous week.

        /// </summary>

        /// <returns>A queryable collection of <see cref="Earthquake"/> objects.</returns>

        public IQueryable<Earthquake> GetEarthquakes()

        {

            var feed = GetFeed(FeedForPreviousWeek);

            var list = new List<Earthquake>();

 

            if ( feed != null )

            {

                foreach ( var entry in feed.Items )

                {

                    var quake = CreateEarthquake(entry);

                    if ( quake != null )

                    {

                        list.Add(quake);

                    }

                }

            }

 

            return list.AsQueryable();

        }

 

        /// <summary>

        /// Creates an <see cref="Earthquake"/> object for each entry in the Atom feed.

        /// </summary>

        /// <param name="entry">The Atom entry.</param>

        /// <returns></returns>

        private Earthquake CreateEarthquake(SyndicationItem entry)

        {

            Earthquake quake = null;

            string title = entry.Title.Text;

            string summary = entry.Summary.Text;

            string point = GetElementValue<String>(entry, "point");

            string depth = GetElementValue<String>(entry, "elev");

            string utcTime = null;

            string localTime = null;

            string depthDesc = null;

            double? magnitude = null;

            double? latitude = null;

            double? longitude = null;

            double? depthKm = null;

 

            if ( !String.IsNullOrEmpty(title) && title.StartsWith("M") )

            {

                title = title.Substring(2, title.IndexOf(',')-3).Trim();

                magnitude = TryParse(title);

            }

            if ( !String.IsNullOrEmpty(point) )

            {

                var values = point.Split(' ');

                if ( values.Length == 2 )

                {

                    latitude = TryParse(values[0]);

                    longitude = TryParse(values[1]);

                }

            }

            if ( !String.IsNullOrEmpty(depth) )

            {

                depthKm = TryParse(depth);

                if ( depthKm != null )

                {

                    depthKm = Math.Round((-1 * depthKm.Value) / 100, 2);

                }

            }

            if ( !String.IsNullOrEmpty(summary) )

            {

                summary = summary.Replace("</p>", "");

                var values = summary.Split(

                    new string[] { "<p>" },

                    StringSplitOptions.RemoveEmptyEntries);

 

                if ( values.Length == 3 )

                {

                    var times = values[1].Split(

                        new string[] { "<br>" },

                        StringSplitOptions.RemoveEmptyEntries);

 

                    if ( times.Length > 0 )

                    {

                        utcTime = times[0];

                    }

                    if ( times.Length > 1 )

                    {

                        localTime = times[1];

                    }

 

                    depthDesc = values[2];

                    depthDesc = "Depth: " + depthDesc.Substring(depthDesc.IndexOf(":") + 2);

                }

            }

 

            if ( latitude != null && longitude != null )

            {

                quake = new Earthquake()

                {

                    Id = entry.Id,

                    Title = entry.Title.Text,

                    Summary = entry.Summary.Text,

                    Date = entry.LastUpdatedTime.DateTime,

                    Url = entry.Links.Select(l => Path.Combine(l.BaseUri.OriginalString,

                        l.Uri.OriginalString)).FirstOrDefault(),

                    Age = entry.Categories.Where(c => c.Label == "Age")

                        .Select(c => c.Name).FirstOrDefault(),

                    Magnitude = magnitude.GetValueOrDefault(),

                    Latitude = latitude.GetValueOrDefault(),

                    Longitude = longitude.GetValueOrDefault(),

                    DepthInKm = depthKm.GetValueOrDefault(),

                    DepthDesc = depthDesc,

                    UtcTime = utcTime,

                    LocalTime = localTime

                };

            }

 

            return quake;

        }

 

        private T GetElementValue<T>(SyndicationItem entry, String name)

        {

            var el = entry.ElementExtensions.Where(e => e.OuterName == name).FirstOrDefault();

            T value = default(T);

 

            if ( el != null )

            {

                value = el.GetObject<T>();

            }

 

            return value;

        }

 

        private double? TryParse(String value)

        {

            double d;

            if ( Double.TryParse(value, out d) )

            {

                return d;

            }

            return null;

        }

 

        /// <summary>

        /// Gets the feed at the specified URL.

        /// </summary>

        /// <param name="url">The URL.</param>

        /// <returns>A <see cref="SyndicationFeed"/> object.</returns>

        public static SyndicationFeed GetFeed(String url)

        {

            SyndicationFeed feed = null;

 

            try

            {

                log.Debug("Loading RSS feed: " + url);

 

                using ( var reader = XmlReader.Create(url) )

                {

                    feed = SyndicationFeed.Load(reader);

                }

            }

            catch ( Exception ex )

            {

                log.Error("Error occurred while loading RSS feed: " + url, ex);

            }

 

            return feed;

        }

    }

}

 
The only method that will be generated in the client side proxy class, EarthquakeContext, will be the GetEarthquakes() method. The reason being that it is the only public instance method and it returns an IQueryable<Earthquake> collection that can be consumed by the client application. GetEarthquakes() calls the static GetFeed(String) method, which utilizes the built in SyndicationFeed API to load the external data feed. You will need to add a reference to the System.ServiceModel.Web library in order to take advantage of the RSS/Atom reader. The API will also allow you to create your own feeds to serve up in your applications.
Model
I have also created a Model folder and added a new class, Earthquake.cs. The Earthquake object will hold the various properties returned from the Atom feed. Here is a sample of the code for that class. Notice the [Key] attribute on the Id property, which is required by RIA Services to uniquely identify the entity.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ComponentModel.DataAnnotations;

 

namespace EarthquakeLocator.Web.Model

{

    /// <summary>

    /// Represents an earthquake occurrence and related information.

    /// </summary>

    [DataContract]

    public class Earthquake

    {

        /// <summary>

        /// Gets or sets the id.

        /// </summary>

        /// <value>The id.</value>

        [Key]

        [DataMember]

        public string Id { get; set; }

 

        /// <summary>

        /// Gets or sets the title.

        /// </summary>

        /// <value>The title.</value>

        [DataMember]

        public string Title { get; set; }

 

        /// <summary>

        /// Gets or sets the summary.

        /// </summary>

        /// <value>The summary.</value>

        [DataMember]

        public string Summary { get; set; }

 

        // additional properties omitted

    }

}

 
View Model
The recent trend to use the MVVM pattern for WPF and Silverlight provides a great way to separate the data and behavior logic out of the user interface layer of your client applications. I have chosen to use the MVVM Light Toolkit for the Earthquake Locator, but there are other options out there if you prefer another library. That said, I went ahead and created a ViewModel folder in the Silverlight project and added a EarthquakeViewModel class that derives from ViewModelBase. Here is the code:

using System;

using System.Collections.ObjectModel;

using System.ComponentModel.Composition;

using System.ComponentModel.Composition.Hosting;

using Microsoft.Maps.MapControl;

using GalaSoft.MvvmLight;

using EarthquakeLocator.Web.Model;

using EarthquakeLocator.Web.Services;

 

namespace EarthquakeLocator.ViewModel

{

    /// <summary>

    /// Provides data for views displaying earthquake information.

    /// </summary>

    public class EarthquakeViewModel : ViewModelBase

    {

        [Import]

        public EarthquakeContext Context;

 

        /// <summary>

        /// Initializes a new instance of the <see cref="EarthquakeViewModel"/> class.

        /// </summary>

        public EarthquakeViewModel()

        {

            var catalog = new AssemblyCatalog(GetType().Assembly);

            var container = new CompositionContainer(catalog);

            container.ComposeParts(this);

            Initialize();

        }

 

        /// <summary>

        /// Initializes a new instance of the <see cref="EarthquakeViewModel"/> class.

        /// </summary>

        /// <param name="context">The context.</param>

        public EarthquakeViewModel(EarthquakeContext context)

        {

            Context = context;

            Initialize();

        }

 

        private void Initialize()

        {

            MapCenter = new Location(20, -170);

            ZoomLevel = 2;

        }

 

        #region Private Methods

 

        private void OnAutoLoadDataChanged()

        {

            LoadEarthquakes();

        }

 

        private void LoadEarthquakes()

        {

            var query = Context.GetEarthquakesQuery();

            Context.Earthquakes.Clear();

 

            Context.Load(query, (op) =>

            {

                if ( !op.HasError )

                {

                    foreach ( var item in op.Entities )

                    {

                        Earthquakes.Add(item);

                    }

                }

            }, null);

        }

 

        #endregion Private Methods

 

        #region Properties

 

        private bool autoLoadData;

        /// <summary>

        /// Gets or sets a value indicating whether to auto load data.

        /// </summary>

        /// <value><c>true</c> if auto loading data; otherwise, <c>false</c>.</value>

        public bool AutoLoadData

        {

            get { return autoLoadData; }

            set

            {

                if ( autoLoadData != value )

                {

                    autoLoadData = value;

                    RaisePropertyChanged("AutoLoadData");

                    OnAutoLoadDataChanged();

                }

            }

        }

 

        private ObservableCollection<Earthquake> earthquakes;

        /// <summary>

        /// Gets the collection of earthquakes to display.

        /// </summary>

        /// <value>The collection of earthquakes.</value>

        public ObservableCollection<Earthquake> Earthquakes

        {

            get

            {

                if ( earthquakes == null )

                {

                    earthquakes = new ObservableCollection<Earthquake>();

                }

 

                return earthquakes;

            }

        }

 

        private Location mapCenter;

        /// <summary>

        /// Gets or sets the map center.

        /// </summary>

        /// <value>The map center.</value>

        public Location MapCenter

        {

            get { return mapCenter; }

            set

            {

                if ( mapCenter != value )

                {

                    mapCenter = value;

                    RaisePropertyChanged("MapCenter");

                }

            }

        }

 

        private double zoomLevel;

        /// <summary>

        /// Gets or sets the zoom level.

        /// </summary>

        /// <value>The zoom level.</value>

        public double ZoomLevel

        {

            get { return zoomLevel; }

            set

            {

                if ( zoomLevel != value )

                {

                    zoomLevel = value;

                    RaisePropertyChanged("ZoomLevel");

                }

            }

        }

 

        #endregion Properties

    }

}

 
The EarthquakeViewModel class contains all of the properties that will be bound to by the various controls in our views. Be sure to read through the LoadEarthquakes() method, which handles calling the GetEarthquakes() method in our EarthquakeService via the EarthquakeContext proxy, and also transfers the loaded entities into the view model’s Earthquakes collection.
Another thing to notice is what’s going on in the default constructor. I chose to use the Managed Extensibility Framework (MEF) for my composition needs, but you can use any dependency injection library or none at all. To allow the EarthquakeContext class to be discoverable by MEF, I added the following partial class so that I could supply the appropriate [Export] attribute:

using System;

using System.ComponentModel.Composition;

 

namespace EarthquakeLocator.Web.Services

{

    /// <summary>

    /// The client side proxy for the EarthquakeService class.

    /// </summary>

    [Export]

    public partial class EarthquakeContext

    {

    }

}

 
One last piece I wanted to point out before moving on to the user interface, I added a client side partial class for the Earthquake entity that contains helper properties that we will bind to later:

using System;

 

namespace EarthquakeLocator.Web.Model

{

    /// <summary>

    /// Represents an earthquake occurrence and related information.

    /// </summary>

    public partial class Earthquake

    {

        /// <summary>

        /// Gets the location based on the current Latitude/Longitude.

        /// </summary>

        /// <value>The location.</value>

        public string Location

        {

            get { return String.Format("{0},{1}", Latitude, Longitude); }

        }

 

        /// <summary>

        /// Gets the size based on the Magnitude.

        /// </summary>

        /// <value>The size.</value>

        public double Size

        {

            get { return (Magnitude * 3); }

        }

    }

}

 
View
Now the fun part! Usually, I would create a Views folder to place all of my View controls in, but I took the easy way out and added the following XAML code to the default MainPage.xaml file. Be sure to add the bing prefix associating the Microsoft.Maps.MapControl namespace after adding the assembly reference to your project.
The MVVM Light Toolkit project templates come with a ViewModelLocator class that you can use via a static resource, but I am instantiating the EarthquakeViewModel directly in my user control. I am setting the AutoLoadData property to true as a way to trigger the LoadEarthquakes() method call. The MapItemsControl found within the <bing:Map> control binds its ItemsSource property to the Earthquakes collection of the view model, and since it is an ObservableCollection<T>, we get the automatic two way data binding via the INotifyCollectionChanged interface.

<UserControl x:Class="EarthquakeLocator.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:bing="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

    xmlns:vm="clr-namespace:EarthquakeLocator.ViewModel"

    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"

>

    <UserControl.Resources>

        <DataTemplate x:Key="EarthquakeTemplate">

            <Ellipse Fill="Red" Stroke="Black" StrokeThickness="1"

                     Width="{Binding Size}" Height="{Binding Size}"

                     bing:MapLayer.Position="{Binding Location}"

                     bing:MapLayer.PositionOrigin="Center">

                <ToolTipService.ToolTip>

                    <StackPanel>

                        <TextBlock Text="{Binding Title}" FontSize="14" FontWeight="Bold" />

                        <TextBlock Text="{Binding UtcTime}" />

                        <TextBlock Text="{Binding LocalTime}" />

                        <TextBlock Text="{Binding DepthDesc}" />

                    </StackPanel>

                </ToolTipService.ToolTip>

            </Ellipse>

        </DataTemplate>

    </UserControl.Resources>

 

    <UserControl.DataContext>

        <vm:EarthquakeViewModel AutoLoadData="True" />

    </UserControl.DataContext>

 

    <Grid x:Name="LayoutRoot">

 

        <bing:Map x:Name="map" CredentialsProvider="--Your-Bing-Maps-Key--"

                  Center="{Binding MapCenter, Mode=TwoWay}"

                  ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}">

            <bing:MapItemsControl ItemsSource="{Binding Earthquakes}"

                                  ItemTemplate="{StaticResource EarthquakeTemplate}" />

        </bing:Map>

 

    </Grid>

</UserControl>

 
The EarthquakeTemplate defines the Ellipse that will represent each earthquake, the Width and Height that are determined by the Magnitude, the Position on the map, and also the tooltip that will appear when we mouse over each data point. Running the application will give us the following result (shown with a tooltip example):
That concludes this portion of our show but I plan on implementing additional functionality in later blog posts. Be sure to come back soon to see the next installments in this series.
Enjoy!
 
Additional Resources

 

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

 

Not too long ago, someone asked me why they couldn't use their mouse to select and copy text from their favorite Silverlight application to paste into other applications they use on a daily basis, like Word or Excel. "I can do it on other web applications, why not yours?" is what I kept hearing during the course of the project. So, because of my desire to make the user happy, along with the fact that I would never hear the end of it if I didn't come up with a solution, I started looking for a way to make our fancy new "Rich Internet Application" behave more like, here it comes... a web page! Ironic, isn't it.

Take a look at this innocent looking Silverlight application with a couple of paragraphs of text:

They might look the same (minus the smiley faces), but the Custom TextBlock allows the user to select some or all of the text content for copy-and-paste operations, using CTRL+C. Here it is in action:

Unfortunately, the TextBlock class is sealed, so I derived the CustomTextBlock control from a TextBox and predefined some settings that would make it appear like a TextBlock. This probably could have easily been done through a Style or a custom ControlTemplate, but I chose to keep all of the property settings in the .cs file for the purposes of this post. Please feel free to use this as is, or offer any feedback or suggestions in the comments.

XAML:

 

<UserControl x:Class="ControlSamples.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:cs="clr-namespace:ControlSamples"

    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"

>

    <Grid x:Name="LayoutRoot" Background="SteelBlue">

        <StackPanel Width="500" Margin="30">

 

            <TextBlock Text="Standard TextBlock :(" FontSize="15" FontWeight="Bold" />

            <TextBlock TextWrapping="Wrap">

                Lorem ipsum dolor sit amet, consectetur adipiscing elit...

            </TextBlock>

 

            <Border Height="1" BorderThickness="1" BorderBrush="Black" Margin="0,20" />

 

            <TextBlock Text="Custom TextBlock :)" FontSize="15" FontWeight="Bold" />

            <cs:CustomTextBlock>

                <cs:CustomTextBlock.Text>

                    Lorem ipsum dolor sit amet, consectetur adipiscing elit...

                </cs:CustomTextBlock.Text>

            </cs:CustomTextBlock>

        </StackPanel>

    </Grid>

</UserControl>

 

CS:

 

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Media;

 

namespace ControlSamples

{

    public class CustomTextBlock : TextBox

    {

        private Border MouseOverBorder;

        private Border ReadOnlyBorder;

 

        public override void OnApplyTemplate()

        {

            base.OnApplyTemplate();

 

            var zeroThickness = new Thickness(0);

            var transparent = new SolidColorBrush(Colors.Transparent);

            var contentElement = GetTemplateChild("ContentElement") as ScrollViewer;

 

            MouseOverBorder = contentElement.Parent as Border;

            ReadOnlyBorder = ((Grid) MouseOverBorder.Parent).Children[0] as Border;

 

            ReadOnlyBorder.Background = transparent;

            ReadOnlyBorder.BorderThickness = zeroThickness;

 

            Background = transparent;

            BorderThickness = zeroThickness;

 

            Padding = zeroThickness;

            TextWrapping = TextWrapping.Wrap;

            IsReadOnly = true;

        }

    }

}

 

UPDATE:  I just came across an old forum post that shows how to do something similar using a ControlTemplate: http://forums.silverlight.net/forums/p/30789/98642.aspx

Enjoy!
Bobby

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati