For this post I thought we'd dig into an example that came up recently with Caliburn.Micro and the TabbedPage view, specifically how to apply MVVM using Caliburn.Micro.
The TabbedPage view that ships with Xamarin.Forms is a great cross-platform page structure that presents sets of content in different tabs. The layout is surprisingly very similar between iOS, Android and Windows 10. The biggest layout difference is seen in iOS where each tab optionally has an icon.
The above image, taken without permission from Xamarin’s documentation, shows iOS, Android and Windows 8.1 Phone. The Windows 10 version is closer to the Android version.
When looking at Xamarin’s documentation, most examples show a series of Pages defined as inline XAML, and applying an ItemTemplate assumes that each tab will have the same layout; there doesn’t seem to be a great way to swap in different pages per tab. Regardless of these short-comings, the most important point regarding these examples is that the TabbedPage view displays the the Title and Icon from the contained Page in the tab headers.
Setting up the ViewModel
The first step in setting up a TabbedPage view is to represent it as its own ViewModel. Caliburn.Micro offers a unique solution to this problem using a pattern it refers to as a Screen Conductor. To understand how this pattern works, Caliburn.Micro treats pages of your application as Screens and the base Screen class contains abstractions for the view lifecycle: OnInitialize, OnActivated, OnDeactivated. These methods, respectively, make it really easy to defer work that shouldn’t be in the constructor, to ensure the view always has the latest data, and to clean-up or prevent navigating away without saving changes. With regards to the TabbedPage, the ScreenConductor provides a simple mechanism to activate and deactivate ViewModels as you navigate between tabs.
Here we define our ScreenConductor as our Main2ViewModel, and Tab1ViewModel and Tab2ViewModel as the contained tabs.
For completeness sake, these new ViewModels are registered in the App class (defined in my previous post):
The XAML definitions for these views are represented as a TabbedPage and two instances of ContentPage:
Fixing View / ViewModels for Tabs
If you run the solution as is, you’ll be disappointed. The reason for this is that while Caliburn.Micro can find the View/ViewModel for our MainPage, it doesn’t know how to resolve the View/ViewModels for the tabs. Now the solution I’m going to use leverages a DataTemplateSelector which isn’t something that is traditionally done with Caliburn.Micro. The correct approach with Caliburn.Micro is to take advantage of conventions and special attached properties (eg View.Model). However in this case, the approach I’m using allows you to use your ContentPage inside the TabbedPage or as a standalone page that you can navigate to directly. I’ll cover the other approach in an upcoming post.
We’ll define a DataTemplateSelector that can do the work of finding the View for our ViewModel:
Then associate into the view:
Now when we run our solution, our tabs are correctly populated.