Caliburn.Micro MVVM with Windows Phone 7

So the ThatConference Chicago Hackathon is over and we ended up failing to impress the judges with our idea, though it seemed more about the fact that we couldn’t get Visual Studio to work properly in 5 minutes versus the actual idea which they said they liked an awful lot, enough to give us honorable mention without proof of a working app.

What I learned from this is the idea is solid and that I really should devote more time and energy into developing it.  And so I started this weekend building up the app we call “Spotted” using MVVM via Caliburn.Micro.  Its been an interesting experience with a lot of learning being done.  So I thought I would share what I learned over the course of the weekend.

A quick word about MVVM

People get confused when the read MVVM, mainly because it has the word “Model” in it.  They seem to think MVVM is a variant or, worse, a replacement for MVC.  It is neither.  MVVM is a client side paradigm where by the interaction logic traditionally housed inside the View (JavaScript and Code Behind files) is transferred to a class which is then “bound” to a view and governs interactions with that view.  The “Model” in this case is merely something coming from another area, it could mimic a “model” in the MVC world, but they are not the same.  In fact, MVVM is closer to MVP rather than MVC, merely because VMs tend to be closely coupled with their views.

So, to summarize, MVC is traditionally found on web servers, where as MVVM is traditionally found on the client side.

The Bootstrapper

I will spare walking you through how to acquire the Caliburn.Micro (CM) DLLs as it is merely a simple nuget command as shown on the project homepage here.  The first thing you need to understand is the bootstrapper. The methods of this class tied to lifecycle events within your Silverlight application.  Out of the box, the default bootstrapper (AppBootstrapper) provides enough for you to get started, including some basic IoC management.  However, I am a huge fan of Ninject, so we will be using that in the application.

The first method we are going to look at is the virtual method Configure provided to us by the PhoneBootstrapper class.  Think of this method as the very first chance you get to do whatever initial setup work the application will need.  In our case, we will be instating the Ninject StandardKernel class as our IoC container.

        private IKernel _ninjectContainer;

        protected override void Configure()
        {
            _ninjectContainer = new StandardKernel(
                         new SpottedNinjectModule(RootFrame));
        }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

To those who are familiar with Ninject, this will make sense.  If you are not familiar, understand that Ninject allows you to specify modules which can then define the mappings for your IoC container.  This is the blueprint for how to fulfill dependencies in your application.  Since this is a Silverlight application, the RootFrame is still used for some purposes even by the underlying CM classes.  This is the source of the SpottedNinjectModule:

        /// 
        /// Loads the module into the kernel.
        /// 
        public override void Load()
        {
            // phone services
            Bind().To().InSingletonScope();
            Bind().ToConstant(
                 new FrameAdapter(_rootFrame)).InSingletonScope();
            Bind().To().InSingletonScope();
            Bind().ToConstant(
                 new PhoneApplicationServiceAdapter(_rootFrame)).InSingletonScope();
        }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

I should point out that, after much trial and error, these are the REQUIRED mappings in order to get CM to work with Ninject.  Be sure to also note the usage of RootFrame for some of the bindings.  This is because CM must still tap into the native features of the platform to carry out certain operations.

Looking at this you will notice that I did not map any view models.  Why?  The reason is CM uses conventions to find view models and views.  This reduces the clutter you traditionally see in the mapping definitions file for a large IoC project.  In fact, this is part of a bigger movement with respect to IoC to reduce the amount of binding type code developers have to write, using conventions to drive the configuration, much the same way this has happened in MVC world.

This page details how CM finds views and view models, I found it extremely helpful.  Basically, if I ask for the HomeView page/view, CM will look to bind the HomeViewModel as the DataContext.  This is all handled automatically leaving the developer to focus on actually implementing the view model instead of the boiler plate code that no one wants to write.

There are three other movies that should be implemented as part of our Ninject Bootstrapper: GetInstance, GetAllInstances, and BuildUp.  The source for these three methods is shown below:

        protected override object GetInstance(Type service, string key)
        {
            return service == null ? null : _ninjectContainer.Get(service);
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return _ninjectContainer.GetAll(service);
        }

        protected override void BuildUp(object instance)
        {
            _ninjectContainer.Inject(instance);
        }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Simple enough.  This will handle the majority of the work within the app layer.  I will update it later, via another post, with the mappings for the service layer.  There I intend to feature Injection by Convention.

Lets Bind a Map

The Spotted App deals heavily with locations extracted from Google Places.  That being the case, it would be useful to show our users a map with pushpins for the locations around them that they might be interested in.  For mapping in Windows Phone 7, Microsoft provides a custom Map control which talks to the Bing Maps SDK, provided you give it an API key (get one here).

It is important to understand that when using CM and adhering to MVVM, the VM should NEVER know anything about the view its binding to.  The idea is to rely purely on Actions and Bindings to carry out all operations.  With that in mind, I present the MainPageViewModel class from Spotted:

        public MainPageViewModel(IEventAggregator eventAggregator)
        {
            // instantiate inner view model
            ResultsMap = new LocalResultsMapViewModel(eventAggregator);

            _eventAggregator = eventAggregator;

            _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High)
                             {MovementThreshold = 20};
            _watcher.PositionChanged += _coordinateWatcher_PositionChanged;

            _watcher.Start();
        }

        private void watcher_PositionChanged(object sender,  
                          GeoPositionChangedEventArgs e)
        {
            _eventAggregator.Publish(
                      new CenterLocationDeterminedMessage(e.Position.Location));
            _coordinateWatcher.Stop();
        }

        public LocalResultsMapViewModel ResultsMap { get; set; }
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

A few things to point on from this class.  First notice we are receiving a class implementing the IEventAggregator interface.  This is provided by CM and represents the hub of the message processing system, more on this in a bit.

Important is that we are instantiating an instance of the LocalResultsMapViewModel which is the view model for a user control on this main page.  Well cover this in-depth shortly.  The big things to take note of in this class is that we are using the GeoCoordinateWatcher to get the current location via the GPS unit.  Once we have it, we pass it in a message to any class listening for this type of message.

Message Passing

Our goal, with any pattern, is to reduce class coupling and promote high levels of cohesion as per the Law of Demeter.  Within MVVM, we may wish to send messages to distinctly different view models, some of which may not even be referenced within the view model publishing the message.  For this, CM provides a Consumer-Publisher model via the IEventAggregator.  You notice above this code:

_eventAggregator.Publish(new CenterLocationDeterminedMessage(e.Position.Location));

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This line tells the _eventAggregator to send a message to any consumer which can handle a message of type CenterLocationDeterminedMessage.  The ability to handle this is denoted by implementing IHandle where T is the type of the message being listened for.  For this I present the LocalResultsMapViewModel.

    public class LocalResultsMapViewModel : ViewModelBase,
          IHandle
    {
        public LocalResultsMapViewModel(IEventAggregator eventAggregator)
        {
            eventAggregator.Subscribe(this); 
        }

        #region Implementation of IHandle

        /// 
        /// Handles the message.
        /// 
        /// The message.
        public void Handle(CenterLocationDeterminedMessage message)
        {
            CenterLocation = message.CenterLocation;
            //if (message.ShowPushpin)
            //{
                Locations.Add(message.CenterLocation);
            //}
        }

         // elided
     }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Here you can see that we communicate that we intend for this class to subscribe for messages being broadcast and that it can handle message of type CenterLocationDeterminedMessage.  The Handle method is then provided to allow us to respond when the message is received.  In this case, I am doing some assignment to local properties who implementations I have excluded from the code sample.

Binding Isnt as straight forward as you think

The following is the XAML present in the LocalResultsMapView user control:

    <Grid x:Name="LayoutRoot">
        <controls:SpottedMapView ZoomLevel="{Binding ZoomLevel}"
                                 CenterLocation="{Binding CenterLocation}"
                                 Locations="{Binding Locations}" />
    </Grid>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This application will make HEAVY use of the map control, thus I decided to wrap it to give me a consistent way to hit its properties.  After learning more about Dependency Properties, I see another avenue that I could have taken with doing this, but it would’ve required some duplication which I do not want.

Before I get into binding, this is the source of SpottedMapView:

    <Grid x:Name="LayoutRoot">
        <map:Map CredentialsProvider="BingAPIKEy"
                 x:Name="theMap" />
    </Grid>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Simple right? Well, if you were to run this code, it would not work.  Even if you define traditional properties to represent ZoomLevel, CenterLocation, and Locations it will not work.  As I found out the hard way, you cannot data bind to normal properties that are custom created.  You can only bind to dependency properties.  Microsoft has really done a great job at hiding this fact in this existing controls, you really cannot tell in a control like TextBlock that you are not binding to Text, but rather, I suspect, TextProperty, which is linked to the traditional Text property.

The answer is to define dependency properties for those custom properties you want to bind to.  Below is all parts to the CenterLocation property I am binding to:

        // Dependency Property
        public static readonly DependencyProperty CenterLocationProperty =
            DependencyProperty.Register("CenterLocation", typeof (GeoCoordinate),
                     typeof (SpottedMapView),
                      new PropertyMetadata(CenterLocationChanged));

        // Traditional Property
        public GeoCoordinate CenterLocation
        {
            get { return theMap.Center; }
            set { theMap.Center = value; }
        }

        // Property Changed Event Handler
        private static void CenterLocationChanged(DependencyObject d,
                                              DependencyPropertyChangedEventArgs e)
        {
            var mapView = (SpottedMapView) d;
            mapView.CenterLocation = (GeoCoordinate) e.NewValue;
        }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

You may look at the direct reference to the control (theMap) and think this is a violation of MVVM.  It is not, we are binding to a custom user control so that we can easily talk to the underlying may.  I could change the mapping system I am using and the external code would be none the wiser.

The part that I learned about is that I can define custom event handling logic when the property changes (via PropertyMetadata).  This allows me to take direct control over the value coming in and how I want to handle it.  For ZoomLevel (not shown) and CenterLocation this is very straightforward and not that interesting.  However, I have a third property which allows me to bind an IEnumerable and have it be translated to Pushpin which are visible on a map:

        private static void LocationsChanged(DependencyObject d,
                     DependencyPropertyChangedEventArgs e)
        {
            var mapView = (SpottedMapView) d;
            mapView.theMap.Children.Clear();
            var style = Application.Current.Resources["userPushpin"] as Style;

            foreach (var location in (IEnumerable)e.NewValue)
            {
                var pp = new Pushpin
                   {
                        Location = location,
                        Content = "You",
                        Style = style
                    };
                mapView.theMap.Children.Add(pp);
            }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This is a very powerful concept and allows me to take full advantage of the decoupling abilities provided by an MVVM framework.  Up next, I intend to create derivations of the Location class so I can denote special meanings, such as style and whether to show a pushpin or not.  At some point, I will also need to start building out the repository layer for communicating with the backend service, and that will require more research into how I might use conventions to reduce the amount of coded needed for that.

Conclusion

I believe strongly that the complexities faced by developers in modern applications are real, and required action.  I think if you take Robert Martins SOLID principles and view the View and the Interactions of the view as responsibilities were were having the View do two things which violated the Single Responsibility Principle.  Separating the view from its interaction logic is not a new thing, but the introduction of using declarative syntax as part of the view to “bind” values and actions, is something that is fairly new.  Using binding we can further separate logic from the view and work toward a more discrete system with respect to responsibilities.

Advertisement

One thought on “Caliburn.Micro MVVM with Windows Phone 7

  1. Thanks very much for posting this. I particularly needed the info for hooking ninject into the caliburn.micro bootstrapper. I appreciate very much your having done all the legwork!

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s