We continue our journey about Caliburn Micro and how to use this MVVM framework in a Windows Phone 8 application by understanding how to manage collections and navigation.
Collections
One of the most common scenarios in a Windows Phone application is the usage of collections: we have a list of data and we need to display it in a ListBox or in LongListSelector. The standard approach is to create a collection in the code (usually, when we’re talking about the XAML world, we use an ObservableCollection) and then to bind it to the ItemsSource property. Guess what? Caliburn Micro has another naming convention for that! And a pretty smart one too! To assign the collection to the ItemsSource property we use the standard naming convention we already know: the name of the property in the view model should match the name of the control. But here comes the smart decision: if you create a property which name is Selected followed by the name of the control in singular, it will be automatically bound with the SelectedItem property of the control. Let’s make an example:
<ListBox x:Name="Items"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding}" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
In the XAML we add a ListBox with a simple template: it will simply display, for every item in the collection, a TextBlock with the value of the single item. The name of the ListBox is Items so, by following the Caliburn Micro naming convention, we expect to have in the view model a property called Items, which is the collection that will be displayed.
private ObservableCollection<string> items; public ObservableCollection<string> Items { get { return items; } set { items = value; NotifyOfPropertyChange(() => Items); } }
And here’s come the magic:
private string selectedItem; public string SelectedItem { get { return selectedItem; } set { selectedItem = value; NotifyOfPropertyChange(() => SelectedItem); MessageBox.Show(value); } }
As you can see the name of the property is SelectedItem, which is the name of the other property in singular (Items –> Item) prefixed by the word Selected. In the setter of the property we’ve added a line of code to display, using a MessageBox, the value of the selected property, that will be helpful for our tests. Now let’s try it: in the public constructor of the view model let’s add some fake data and assign it to the Items property.
public MainPageViewModel() { Items = new BindableCollection<string> { "Matteo", "Mario", "John" }; }
If you launch the application and you’ve correctly followed the steps the ListBox will contain the three test values we’ve defined: by tapping on one of the elements a MessageBox with the selected name will appear.
Navigation
Usually managing the navigation from a page to another of the application is one of the trickiest tasks using MVVM. The biggest problem to face is that in Windows Phone we use the NavigationService, that can be used only in the code behind that is connected to a view. You can’t directly access to it from another class, for example, like a ViewModel. To support the developer Caliburn Micro comes with a built in NavigationService, that can be used in a ViewModel by simply adding a reference in the public constructor of the application. The built in dependency injection container will take care of resolving the dependency for you and will give you access to it. So, the first thing to use manage navigation in a MVVM application developed with Caliburn Micro is to change the public constructor of your view model, like in the following example:
public class MainPageViewModel : PropertyChangedBase { private readonly INavigationService navigationService; public MainPageViewModel(INavigationService navigationService) { this.navigationService = navigationService; } }
From now on, you’ll be able to use the NavigationService inside your view model: you won’t have to register anything in the bootstrapper, since Caliburn Micro will take care of everything (as for every other service that is embedded with the toolkit).
The NavigationService that comes with the toolkit supports a view model first approach: instead of declaring which is the URL of the page where we want to take the user (that is the standard approach), we declare which is the ViewModel we want to display. The service will take care of creating the correct URL and display the view that is associated with the view model. Let’s see how does it work.
First we need a new page where to redirect the user: add to your project a new page and a new class in the ViewModels folder. Remember to use the naming convention we’ve learned in the second post of the series: if the name of the page is Page2View.xaml, the name of the ViewModel will have to be Page2ViewModel.cs. Before moving on, you have to remember to register the new view model in the Configure method of the bootstrapper, like in the following example:
protected override void Configure() { container = new PhoneContainer(RootFrame); container.RegisterPhoneServices(); container.PerRequest<MainPageViewModel>(); container.PerRequest<Page2ViewModel>(); AddCustomConventions(); }
Now add a button in the main page of your application and, using the naming convention we’ve learned in the previous post, assign to it a method in your view model, that will trigger the navigation towards the second page.
<Button Content="Go to page 2" x:Name="GoToPage2" />
public void GoToPage2() { navigationService.UriFor<Page2ViewModel>() .Navigate(); }
With the UriFor<T> method of the navigation service we get the needed URL for our view model, then we call the Navigate()method to trigger the navigation and redirect the user to the requested page.
Navigation with parameters
You should already know that, when navigating from a page to another, you are able to carry some parameters in the query string, that can be used in the new page, like in the following sample
/Page2View.xaml?Name=Matteo
Using the NavigationContext you are able, in the view Page2View.xaml, to retrieve the value of the Name property that is passed using a query string. How to do it using Caliburn Micro? The first thing is to define, in our destination page’s view model (in our example, the class Page2ViewModel) a property, that will hold the value of the parameter.
public class Page2ViewModel: PropertyChangedBase { private string name; public string Name { get { return name; } set { name = value; NotifyOfPropertyChange(() => Name); } }
Then, we change the navigation operation like this:
public void GoToPage2() { navigationService.UriFor<Page2ViewModel>() .WithParam(x => x.Name, "Matteo") .Navigate(); }
We’ve added the method WithParam, that accepts two parameters: the first one is the property of the destination view model that will hold our value and it’s specified using the lambda syntax (x represents the destination view model, in our example the instance of the Page2ViewModel class); the second parameter is the value that the property will have. When the Page2View.xaml view will be loaded, the Page2ViewModel will hold, in the Name property, the value of the parameter we’ve passed during the navigation, so we can use it for our purposes. For example, we can simply display it by adding a in the XAML a TextBlock with the same name of the property (do you remember the naming convention?)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel> <TextBlock x:Name="Name"></TextBlock> </StackPanel> </Grid>
Important! It’s true that Caliburn Micro does a lot of magic, but the navigation with parameter feature is still based on query string parameter. The only magic is that these parameters are automatically injected in the properties of your view model, but they still are strings: you can use the WithParam method to pass to the new view just plain values, like strings and number. You can’t use it to pass complex objects.
To be continued
The journey is not ended yet, we still have to see how to manage messages and tombstoning with Caliburn Micro. Coming soon in the next posts! While you wait, you can download the sample project and start playing with it!
The Caliburn Micro posts series
Hi Matteo,
Fantastic posts, really enjoying them. And the speed of your posts is great too. Keep it up!
Really helpful posts. You are making easier to apply Caliburn.Micro into a WP8 application. Please, keep it up!
Just a brief question. As you mentioned “You can’t use it to pass complex objects.” related to WithParam, then do you have any suggestions about how to send a complex object? One could be serializing the object, but do you have any other alternative using Caliburn.Micro instead?
Hi, one way would be to create your own class to store the objects you need to send between different views. This class would be registered as a singleton and automatically injected to every view model, by passing it as a parameter in the constructor. By the way, it’s a topic I would analyze in one of the next posts 🙂
Thank you very much Matteo. It sounds a good a idea to keep a memory cache with the objects to exchange. I’m looking to forward to reading next posts 🙂
Great post, been following through as I’m new to Caliburn.Micro and MVVM (but not to .NET).
One thing I’d like to point out, that affected me as I followed your example on the Page2 name. This wouldn’t work for me, and Caliburn was throwing an exception saying the viewmodel didn’t exist. As soon as I renamed it (ie, without a number) it worked fine. I’m guessing the current release of Caliburn.Micro doesn’t like numbers in the view or viewmodel name?
Hi, usuanlly the rule to apply should be to add the suffix View to the name of the page and ViewModel to the name of the ViewModel. So, you should have a page called Page2View.xaml and a ViewModel called Page2ViewModel.cs. Using these names the connection between ViewModel and View works, I’ve tried with a sample project and I’m able to correctly navigate to the second page.
Hi Matteo,
I’m facing a problem using your examples. I googled a lot but I didn’t find a solution cause I don’t understand what is really happening.
I created a simples example where there are 2 pages.
The first one is a listbox that contains a list of names. It’s binded to a property in my viewmodel using caliburn name conventions.
So I selected the item and again using the name conventions I pass that item to the page 2. It’s the id of the item. At page 2 I am able to make some changes and I save the selected object to the database. I did the db.submitchanges !!.. But when I go back to the first page using navigationService.GoBack(); the item hasn’t changed at all. I don’t know what is happening cause the listbox is binded to a observable collection but it doesn’t change.
I only changes when I add a new item to the list. When I update that determined item nothing happens. I have to close the app and open it again.
could you help !… thanks in advance !
Well.. I found the problem hehe…
My database connection was inside the viewmodel’s constructor.. then I moved it to the onactive overrided method and everything worked fine.. But in my mind the instance of the database should still be inside the constructor…whatever hehe..thanks sirs..
protected override void OnActivate()
{
db = new BDContext(BDContext.ConnectionString);
var q = from …..
LstAtividades = new ObservableCollection(q);
}
I’m glad you’ve solved your issue!
Matteo,
If my app uses a eventaggregator in the mainpage.xaml and the app goes to tombstone state… when I restore the app the eventaggregator is gone ?
I found this issue(or mine) when trying to trigger a message from page2 to mainpage.xaml…. if my app goes to tombstone the message isn’t triggered anymore to mainpage.
I guess that the problem is that, in case of tombstone, the MainPage is not in memory anymore, so it’s not able to receive messages. You should make sure that the MainPage has been initialized after a tombstone.
Hi Matteo, great posts.
I’m trying to figure out Caliburn and i tried to use your example just except ListBox I used LongListSelector and it doesen’t work(but it works with ListBox). VM is the same as yours.
Is there any trick or known bug?
Hi, I guess it’s not a bug, but it’s just that Caliburn doesn’t offer a built in convention for the LongListSelector. However, using the ConventionManager in the boostrapper, you can create it. You can find more details in the documentation (https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions). Moreover, if you install the Caliburn.Micro.Start NuGet package you’ll notice that the sample bootstrapper added to he project uses the ConventionManager to handle the SelectedItem property of the Panorama and Pivot controls. I’ve posted the code here: http://pastebin.com/hd3qzCRb It should be easy to adapt it to the LongListSelector control.
Thank you very much I’ll try to play with it and make it work 🙂
Hi, I have a question about the navigation part. Will method described in this tutorial work for ApplicationBar buttons? Let me clarify a bit. I have, let’s say MainPageView with ApplicationBar menu (with couple buttons for CRUD like operations). I’d like to use buttons from that menu to switch to different views. Is this possible with Caliburn.Micro?
Lol, please ignore my previous post, I’ve found the solution:)
Good to know 🙂 However, I talked about your scenario in the following post: http://wp.qmatteoq.com/first-steps-with-caliburn-micro-the-application-bar/
Thanks, you’re doing great work here.
Thanks very much!