Click here to Skip to main content
Click here to Skip to main content

Caliburn Micro for Windows Phone 7

, 20 Apr 2011
Rate this:
Please Sign up or sign in to vote.
Convention-over-configuration MVVM in the Windows Phone 7 world.

Introduction

The Caliburn Micro framework is one of the first MVVM frameworks to become available for Widows Phone 7 (WP7) and it stands out as very productive thanks to the extensive use of "convention over configuration".

Background

When creating Silverlight applications for Windows Phone 7, you are offered basic MVVM implementations, but most will find these inadequate for any non-trivial application. I do recommend that people learning WP7 Silverlight programming do not use a framework, as this will expose them more to the platform, and make them appreciate more the frameworks they are using.

About the Caliburn Framework

The original Caliburn was created as an Open Source (MIT License) MVVM Framework for WPF and later adopted for Silverlight. At the time of writing of this article, the project has reached version 2.0. And right on the project page, you'll find the following message:

"STOP! If you are new here, we recommend a simpler framework with 90% of the features and power! Check out Caliburn.Micro".

Also, the name "Caliburn" is one of the different spelling/names of "Excalibur".

Enter Windows Phone 7

Now if you are developing for Silverlight and WP7, it is a natural fit to choose a lighter framework, and if you are starting development, why not choose a simpler one as well?

Here's some extra information about the history and creation of this framework: link.

Alternative WP7 MVVM Frameworks

There are a few other popular MVVM frameworks out there:

The Problem We're Solving

I'll demonstrate the benefits of using the Caliburn Micro framework by writing a WP7 client monitoring the statuses of Cruise Control.Net.

If you are familiar with what Cruise Control.Net (CC) does, I recommend following the link above, but the elevator pitch would be "It is an Automated Continuous Integration server, monitoring your source control, running builds, tests, and code analysis, helping maintain your product in a state of successful build and correct function" - it's free, Open Source, and written in .NET.

Using the Caliburn Micro for Windows Phone 7

The download page of Caliburn Micro project contains a single package that you have to download and install that will install the binaries, samples, and Visual Studio templates you'll need.

At the time of writing of this article, this project was not in NuGet, but I'm guessing you'll soon be able to get it that way, too.

Update

April 20 2011: Now there is a Caliburn Micro NuGet package (and here's an article about adding NuGet support for Caliburn Micro).

If you have NuGet 1.2 or later installed, from the Package Manager Console, just type:

Install-Package Caliburn.Micro

Empty Project

As part of the Visual Studio Templates installed, you'll find this project template:

ccnetwp7_1a.png

After project creation, the readme.txt file will be opened pointing you to the Caliburn Micro project page, which is currently the best place to look for documentation.

The project tree will look something like this:

ccnetwp7_2.png

Notice a new folder "Framework", new class "AppBootstrapper", and a Main Page with no code-behind.

The "Framework" folder contains the entire Caliburn Micro source code below it (and there are no extra assembly references):

ccnetwp7_3.png

App and App Bootstrapper

App.xaml has been left almost entirely empty - there's only an AppBootstrapper reference in the resources and this is because all the logic from this class has been redirected to AppBoostrapper, which will do a few extra things for us.

IoC/Dependency Injection

As part of a new project, you'll also get a basic Inversion of Control container, but you can replace that container with any other for WP7. To setup the container, AppBootstrapper contains the basic code in the overridden Configure method:

protected override void Configure()
{
    container = new PhoneContainer(this);

    container.RegisterPerRequest(typeof(MainPageViewModel), 
              "MainPageViewModel", typeof(MainPageViewModel));

    container.RegisterInstance(typeof(INavigationService), 
              null, new FrameAdapter(RootFrame));
    container.RegisterInstance(typeof(IPhoneService), null, 
              new PhoneApplicationServiceAdapter(PhoneService));

    //container.Activator.InstallChooser<phonenumberchoosertask,>();
    //container.Activator.InstallLauncher<emailcomposetask>();

    AddCustomConventions();
}
Convention Over Configuration

Now, about the AddCustomConventions method: modern development tends to be a very repetitive and verbose process that we have learned to put up with mostly due to the vastly improved IDEs. XAML platforms (WPF, WF, Silverlight, WP7) are no exceptions. You'll often find yourself repeating common patterns - like this one:

<Button x:Name="Insert" 
    Content="insert" 
    IsEnabled="{Binding CanInsert}"
    Click="Insert_OnClick"/>

And this is just a tip of the iceberg. All of this is due to the fact that you can set all of these properties and bindings in a variety of ways and due to increased flexibility, we have increased complexity. To deal with complexity, developers tend to use patterns to simplify their mental maps of projects (like in the case above - notice that there's an "Insert" text in every line). Why just not go a step further and say: we'll use these conventions by default and maybe all we need is:

<Button x:Name="Insert" 
        Content="insert"/>

Caliburn Micro gives you this goodness, thanks to the usage of those conventions and much more:

In the sample above, we had to have a method in the code-behind of the page that would have had to then find the ViewModel and invoke the extra "Insert" method. With CM, you'll get all of that for free:

  • By having a public Insert method in your View Model, CM will make sure that a click from the button "Insert" is passed to the ViewModel's Insert method
  • If you have a public method returning a bool or a property of type bool with the name "CanInsert", CM will bind this to the IsEnabled property of the button

And this is just the start...

Yes, but!

But what if you don't like conventions that have been setup for you, or you need to override them in some cases?

  • Every binding and value that is set before Caliburn Micro attaches to a View Model that will not be changed. This allows to you to override convention bindings.
  • There is an API to change conventions, and you can check out an article about it here: link.

Pairing Views and View Models

Since in WP7 you are navigating between pages, they (as views) will be the one that will need to somehow find and initialize the appropriate View Models. Again, you are most likely to have "AddServerPage.xaml" and "AddServerPageViewModel.cs" as a pair so let's allow Caliburn Micro to pair these up for us:

container.RegisterPerRequest(typeof(AddServerPageViewModel), 
          "AddServerPageViewModel", typeof(AddServerPageViewModel));

By just doing this, you will, whenever you navigate to your AddServerPage, have your ageView model ready and waiting in the page's DataContext.

Base Classes for View Models

It's possible to use just plain-old-CLR-objects (POCO) for your View Models. In most cases, you will want to implement the INotifyPropertyChanged interface. Caliburn Micro has quite a few base classes for View Models to choose from:

  • PropertyChangedBase - is a basic implementation of INotifyPropertyChanged with a few extras
  • Screen - which offers a lot of events that you can hook-up to, like OnActivate, and the option to reach-out to attached views
  • Conductor - and especially Conductor<t>.Collection.OneActive are pre-assembled View Model kits for when you are working with collections
Using ProperyChangedBase

Let's dig into AddServerPageViewModel and have it derive from this base class:

public class AddServerPageViewModel : Framework.PropertyChangedBase
{
    public AddServerPageViewModel()
    {
        _fullAddress = new Uri("http://ccnet.wheelmud.net" + 
                               "/XmlServerReport.aspx");
    }

    Uri _fullAddress;
    
    public string ServerName
    {
        get { return _fullAddress.Host; }
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentNullException();

            var ub = new UriBuilder(_fullAddress)
            {
                Host = value,
            };

            SetFullAddress(ub.Uri);
        }
    }

    public int PortNumber
    {
        get { return _fullAddress.Port; }
        set
        {
            var ub = new UriBuilder(_fullAddress)
            {
                Port = value,
            };

            SetFullAddress(ub.Uri);
        }
    }

    public string FullAddress
    {
        get { return _fullAddress.ToString(); }
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentNullException();

            SetFullAddress(new Uri(value));
        }
    }

    void SetFullAddress(Uri uri)
    {
        _fullAddress = uri;

        CanAddServer = _fullAddress.IsAbsoluteUri;

        Refresh();
    }
}

We have three properties that are all tied together and help with editing of the server URL. Since all three are likely to change together, I am just calling the Refresh method from PropertyChangedBase, which will notify the View that all of these properties have changed. Handy. For View, all we need to add to ContentPanel is:

<StackPanel>
    <TextBlock><Run Text="Server DNS:"/></TextBlock>
    <TextBox x:Name="ServerName" InputScope="Url" />
    <TextBlock><Run Text="Server Port:"/></TextBlock>
    <TextBox x:Name="PortNumber" InputScope="Number"/>
    <TextBlock><Run Text="Full Uri:"/></TextBlock>
    <TextBox x:Name="FullAddress" InputScope="Url"/>
</StackPanel>

Tip: When testing this page in the emulator, instead of typing with mouse on the on-screen keyboard, you can press Page Up on the computer's keyboard to simulate the phone's hardware keyboard.

Using Conductor<t>.Collection.OneActive

Inheriting your View Model from this base class allows for simple creation of View Models for collections and in this variation, it allows and maintains only one active child.

This base class has an Items property which is matched in the View with a List Box or another Items control-derived class with x:Name="Items". The active item is bound to SelectedItem.

Problem of Navigation

You might have noticed that pages and behaviours (if using Blend) have the power to navigate to other pages, but this is not really desirable, as we want View Models to be in control of navigation. After navigating to a page, it can access the passed arguments by parsing the query string parameters, but again - this is what we want the View Model to do.

Caliburn Micro solves these two problems by exposing a rich INavigationService interface and making it easily available to your View Models, thanks to the beauty of Dependency Injection. All you need to get this service in your View Model is to have a constructor that will take this interface as a parameter:

public AddServerPageViewModel(INavigationService navigationService) {/*…*/}

The second part of the problem (parsing the arguments passed) is handled automatically, thanks to Convention over Configuration. If you are navigating to the URI "/ProjectPage.xaml?projectId=17" and you have a ProjectId property on the View Model for that page, Caliburn will auto-magically populate the ProjectId property with the specified parameter value.

If you need more control over the lifetime of the View in your View Model, just inherit from the Screen base class or one of the more specialized sub-classes.

WP7's Un-Bind-able Application Bar

Part of the WP7 UI is the Application Bar, which in this first version is not bind-able. As a matter of fact, you'll have to use indexes to manipulate buttons, usually directly from your View.

Caliburn Micro for WP7 provides significant improvements in this area - allowing you to bind the App Bar to your View Model.

What you'll need to do is just use CM's AppBarButton and AppBarMenuItem inside the standard App Bar and then identify what it needs to bind to by specifying the "Message" property.

I like to do this from inside of Blend and thankfully you can do that too. Open your project in Blend (you can right-click on the project in Visual Studio to do this), then open the page you want to add an App Bar to, and select the page in the objects list:

ccnetwp7_4.png

Then in Properties, section "Common Properties":

ccnetwp7_5.png

Press the "New" button to create the application bar:

ccnetwp7_6.png

Notice the "Buttons (Collection)" and press the "..." button to edit a collection of buttons that will be in this application bar:

ccnetwp7_7.png

Now "Add another item" has a drop-down button and if you want the WP7 framework's button, you can use this drop-down, but to use Caliburn Micro's improved version of this button, just press the "Add another item" button. Select the Object dialog that will pop-up:

ccnetwp7_8.png

I often find that just typing "AppBar" will quickly filter out unwanted types:

ccnetwp7_9.png

Select AppBarButton from that list and press the OK button.

The added items will look a lot like the standard button:

  • The IconUri drop down allows for quick selection of images, including stock ones, which will also be added to the project, if needed
  • Text to be displayed below the button

One extra Message field is Caliburn Micro's field that maps to the Command in the View Model.

ccnetwp7_10.png

Not only does this auto-map to the "Message" Command (method), but also to "CanMessage" (method of property) that will control enabling or disabling of the button.

C# 5 is a Long Way Away

Ever since Silverlight 2, the only way to make Web Service calls was asynchronously. Because we have a multi-threading environment and Microsoft is preventing us from shooting ourselves in the foot by forcing us to do all UI activities on the UI thread, doing network calls in WP7 is slightly painful. C# 5 is going to introduce the "async" keyword which is going to make this much easier, but right now, we have to program without it.

This introduces a few big questions:

  • How can we leave as much as possible inside MVVM and keep it clean
  • How to offload all the possible processing on non-UI threads to keep an app performing
  • How to present the user with good loading/processing feedback and possibly an option to cancel it

One of the trickiest questions for me was:

  • How to control animations to indicate busy status from the View Model

And then I found this blog post: link. Caliburn Micro has this great feature that methods which are called to invoke commands could also return IEnumerable<IResult>. Caliburn will go over the returned results, executing them one by one until they are all completed, one cancels, or throws/returns an error. This by itself is great, but there's more! The IResult interface looks like this:

public interface IResult
{
    void Execute(ActionExecutionContext context);
    event EventHandler<resultcompletioneventargs> Completed;
}

public class ActionExecutionContext
{
    public ActionMessage Message;
    public FrameworkElement Source;
    public object EventArgs;
    public object Target;
    public DependencyObject View;
    public MethodInfo Method;
    public Func<bool> CanExecute;
    public object this[string key];
}

Thanks to the Source property in ActionExecutionContext we have access to objects on the actual page. This is good unless you abuse it - so try to keep it simple and no tight coupling. My choice is to use the Visual State Manager to change the state of the page:

public IEnumerable<iresult> AddServer()
{
    CanAddServer = false;
    Refresh();

    yield return new SetVisualState("Adding");

    var readServer = _readServerFactory();
    readServer.Server.Name = ServerName;
    readServer.Server.Uri = FullAddress;

    yield return readServer;

    _serverDataManager.AddServer(readServer.Server);

    yield return new SetVisualState("Default");

    CanAddServer = true;
    _navigationService.GoBack();
}

This sample is from AddServerPageViewModel and it will execute these operations asynchronously, but for me, the best part is the fact that I can easily and cleanly manipulate Visual State:

public class SetVisualState : IResult
{
    readonly string _state;
    public SetVisualState(string state)
    {
        _state = state;
    }

    public void Execute(ActionExecutionContext context)
    {
        VisualStateManager.GoToState((Control)context.View, _state, true);

        Completed(this, new ResultCompletionEventArgs());
    }

    public event EventHandler<resultcompletioneventargs> Completed = delegate { };
}

Conclusion

Caliburn Micro is a fantastic framework for WP7, and it holds the promise of great productivity boost, but still it has some issues that will turn away a lot of people:

  • It will probably restructure your code differently than what you might be used to
  • Sometimes you'll waste time trying to figure out how to perform an action which looks to you pretty straightforward
  • Working with design-time data and Caliburn Micro is possible, but those pages will have to use standard XAML bindings

Notes

  • I'm always setting Input Scope on Text Boxes and if Visual Studio does not give you intelli-sense, Blend will.
  • Your app should start fast. Don't use a splash screen unless you are certain that you need it.
  • You can find some great articles that serve as Caliburn Micro documentation here: http://caliburnmicro.codeplex.com/documentation.
  • This project will continue on: http://ccnetwp7.codeplex.com/ to add features like:
    • Ability to monitor and control builds from your phone
    • Live Tile + Server Notifications

History

  1. May 31 2011 - Initial version.
  2. April 4 2011 - Minor clean-ups.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

mega_me
Software Developer (Senior)
New Zealand New Zealand
No Biography provided
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmvpKanasz Robert21-Sep-12 3:29 
GeneralMy vote of 5 PinmemberCrezber13-Dec-11 6:38 
GeneralMy vote of 5 PinmemberPeter Klutch1-Apr-11 19:21 
GeneralRe: My vote of 5 Pinmembermega_me2-Apr-11 16:17 
GeneralGood stuff PinmvpSacha Barber1-Apr-11 5:37 
GeneralRe: Good stuff Pinmembermega_me2-Apr-11 16:18 
GeneralRe: Good stuff PinmvpSacha Barber2-Apr-11 19:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 20 Apr 2011
Article Copyright 2011 by mega_me
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid