First steps with Caliburn Micro in Windows Phone 8 – The Application Bar

The Application Bar is the joy and the pain of every Windows Phone developer that starts to use the Model-View-ViewModel pattern to develop his applications. Sooner or later you’ll have to face this problem: the Application Bar is a special control, that doesn’t belong to the page and that doesn’t inherit from the FrameworkElement class. For this reason, binding simply doesn’t work: you can’t use commands to hook to the Tap event and you can’t bind a property of your ViewModel to the Text or IconUri properties.

Since in most of the cases dealing with the Application Bar will simply force you to “break” the MVVM pattern, many talented developers came up with a solution: an Application Bar replacement. There are many implementations out there: two of the bests I’ve found are the Cimbalino Toolkit by Pedro Lamas and Caliburn Bindable App Bar by Kamran Ayub. The first toolkit uses an approach based on behaviors, that are applied on the native application bar. We won’t discuss about this toolkit in this post because, even it’s great, it doesn’t play well with Caliburn: it’s been designed with support for MVVM Light in mind. We’ll focus on the Caliburn Bindable App Bar, which is a totally new control that replaces the standard Application Bar and that supports all the standard Caliburn naming conventions.

Let’s start!

Add the application bar to a project

The Caliburn Bindable App Bar is available as a NuGet package: simply right click on your project, choose Manage NuGet packages and look for the package called Caliburn.Micro.BindableAppBar. Once you’ve installed it, you’ll have to add the following namespace in the XAML to get a reference to the control:

xmlns:bab=”clr-namespace:Caliburn.Micro.BindableAppBar;assembly=Caliburn.Micro.BindableAppBar”

Once you’ve done it, you can add the real control which is call BindableAppBar. But, pay attention! Unlike the real ApplicationBar control (that is placed outside the main Grid,  because is not part of the page), this control should be placed inside the Grid, right before the closing tag (I’m talking about the Grid that, in the standard template, is called LayoutRoot). Here is a sample:

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!--TitlePanel contains the name of the application and page title-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock Text="Caliburn Micro" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
        <TextBlock Text="Sample" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

    </Grid>

    <bab:BindableAppBar x:Name="AppBar">
        <bab:BindableAppBarButton x:Name="AddItem"
                                  Text="{Binding AddItemText}"
                                  IconUri="{Binding Icon}" 
                                  Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
                                  />

        <bab:BindableAppBarMenuItem x:Name="RemoveItem"
                                  Text="Remove"
                                  />
    </bab:BindableAppBar>
</Grid>

The control is very simple to use! Inside the BindableAppBar node you can add two items: BindableAppBarButton, which is the icon button (remember that you can add up to four icons) and BindableAppBarMenuItem, which is one of the text items that are displayed under the icons.

They share the same properties, because they both are buttons that can be tapped to trigger an action: the only difference is that the BindableAppBarButton control has an IconUri property, which contains the path of the image that is displayed as icon.

Both controls share the same Caliburn naming convention that is used for actions: the value of the x:Name property of the control is the name of the method, declared in the ViewModel of the page, that is triggered when the user taps on it. The best part of this control that all the other properties supports binding, even the Visibility property, that can be used to show or hide an item according to some conditions.

Before using it, there’s an important step to do: add a custom convention. Caliburn Micro supports a way to define your own custom conventions, that are added at the top of the already existing one. The place where to do this is in the boostrapper, inside the AddCustomConventions() method that, by default, is called when the boostrapper is registered.

Here is the code to insert:

static void AddCustomConventions()
{
    ConventionManager.AddElementConvention<BindableAppBarButton>(
    Control.IsEnabledProperty, "DataContext", "Click");
    ConventionManager.AddElementConvention<BindableAppBarMenuItem>(
    Control.IsEnabledProperty, "DataContext", "Click");
}

With this code basically we’re adding a convention to manage the Click event on the button, so that it’s enough to give to a method the same name of the Button control to bind them together.

Now it’s the ViewModel’s turn to manage the BindableAppBar:

public class MainPageViewModel: Screen
{

    private string addItemText;

    public string AddItemText
    {
        get { return addItemText; }
        set
        {
            addItemText = value;
            NotifyOfPropertyChange(() => AddItemText);
        }
    }

    private Uri icon;

    public Uri Icon
    {
        get { return icon; }
        set
        {
            icon = value;
            NotifyOfPropertyChange(() => Icon);
        }
    }

    private bool isVisible;

    public bool IsVisible
    {
        get { return isVisible; }
        set
        {
            isVisible = value;
            NotifyOfPropertyChange(() => IsVisible);
        }
    }

    public MainPageViewModel()
    {
        AddItemText = "Add";
        Icon = new Uri("/Assets/AppBar/appbar.add.rest.png", UriKind.Relative);
        IsVisible = false;  
    }

    public void AddItem()
    {
        MessageBox.Show("Item added");
    }

    public void RemoveItem()
    {
        MessageBox.Show("Item removed");
    }
}

Nothing special to say if you’ve already read the other posts about Caliburn Micro: we have defined some properties and methods, that are connected to the Application Bar using the Caliburn naming conventions. When the ViewModel is created, we set the text, the icon and the visibility status of one of the buttons in the Application Bar, instead of defining them in the XAML. This approach is very useful when the state of the buttons in the Application Bar needs to change while the application is executed. For example, think about an application to read news: the user is able to save a news in the favorites’ list using a button in the Application Bar. In this case, the status of the button should change according to the status of the news: if the news has already been marked as read, probably the text of the button will be something like “Remove” and the icon will display a minus sign; vice versa, if the news hasn’t been added yet to the list the button’s text will say “Add” and the icon will display a plus sign.

With the ViewModel we’ve defined, it’s simple to change some properties according to the status of the news and automatically reflect the change to the control in the XAML.

Also the Visibility property can come in handy in many situations: for example, let’s say that the same news application as before allows the user to pin a news in the start screen, by creating a secondary tile. In this case, only if the application is opened from a secondary tile we want to display a “Home” button in the Application Bar, to redirect him to the home page of the app; otherwise, we don’t need it, because the user can use the Back button to accomplish the same task. In this scenario, the Visibility property is perfect for us: it’s enough to change it according to the fact that the app has been opened from a secondary tile or not.

 

The Caliburn Micro posts series

  1. The theory
  2. The first project
  3. Actions
  4. Collections and navigation
  5. Tombstoning
  6. Advanced navigation and deep links
  7. Messaging
  8. Using launchers and choosers
  9. Use your own services
  10. The Application Bar
  11. Pivot
  12. Lazy loading with pivot
This entry was posted in Windows Phone and tagged , , . Bookmark the permalink.

14 Responses to First steps with Caliburn Micro in Windows Phone 8 – The Application Bar

  1. masantiago says:

    It rocks! What a great series of posts! Please, keep it up!

    I’m going to try out this new application bar. Is it also possible to hide the whole bar when moving between screens using binding? Imagine a panorama or pivot control when sliding. As far as I know, the effect of hiding with the traditional bar is not smooth. In addition, there is a bug with panorama in WP8 that the SelectedChanged event does not raise when using binging of Screens in a conductor. How would you proceed with that?

    Other topic. Have you realized that a new version (1.5.0) of Caliburn is available? It seems that it deals with the async/await pattern with coroutines. It would be great if you could write in the future a post about how to deal with these concepts in WP8.

    Thank you very much for all your help!

    • qmatteoq says:

      Hi Miguel, thanks for your feedback, I’m happy that you like these posts 🙂
      About your questions, the scenarios you talked about is something I’m going to investigate in the next days, since I would like to expand these series of posts. And this involves both conductors and routines.
      The bad news is that, as far as I know, the conductors approach isn’t working correctly with Panoramas on WP8 due to the OS bug you’ve mentioned.

  2. Kamran says:

    Thanks for the tutorial! Glad you like the component.

    I noticed the Panorama had issues on WP8 and was wondering what was going on. If I find time, I would like to investigate ways to workaround it, since I’m working on a small little app that uses a Panorama and it would motivate me to try and fix the problem.

    • qmatteoq says:

      I think that all depends from a bug that the Panorama control has in WP8: if, instead of manually adding the PanoramaItems in the XAML or in the code behind, you add them using a collection and the ItemsSource property of the control, the index is messed up. No matter how many panorama items you have, the SelectedIndex will always return you just the values 0 and 1, instead of the correct one.

  3. at says:

    Got a compile error:
    The name “BooleanToVisibilityConverter” does not exist in the namespace “http://www.caliburnproject.org”.

    Can you help?

    • qmatteoq says:

      The correct namespace to use the BooleanToVisibilityConverter included in Caliburn is “xmlns:micro=”clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro””

      • camillo says:

        I’ve correct namespace xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
        but i’ve error

        The resource "BooleanToVisibilityConverter" could not be resolved.

        • camillo says:

          My solution:
          App.xaml
          add row

          xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"

          Now in page.xaml it’s work!

  4. Krystian says:

    Once I try to install the CM BAB i got this error: You are trying to install this package into a project that targets ‘Silverlight,Version=v4.0,Profile=W
    indowsPhone71’, but the package does not contain any assembly references that are compatible with that framework. I write for Windows phone 7.1, any help?

    • qmatteoq says:

      Strange, I’m using it in a Windows Phone 7.1 project without any issue. Is NuGet updated to the latest version? Which Visual Studio version are you using?

  5. Dave H says:

    Great stuff implemented into WP8 MVVM in 1hr 10. Fabulous tool.

Leave a Reply to at Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.