Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Xamarin

Using MVVM to create Tabbed Interfaces with Calcium for Xamarin Forms

5.00/5 (10 votes)
17 Sep 2014CPOL8 min read 54.3K  
Create a Xamarin Forms tabbed page or carousel page by binding to a collection of ViewModels; extending beyond the current capabilities of Xamarin Forms.

Calcium for Xamarin Forms

Introduction

Xamarin.Forms is Xamarin’s new cross-platform UI framework that allows you to build user interfaces for iOS, Android and Windows Phone using XAML.

In the previous part of this series we looked at the origins of Calcium and took a peak at some of its key features. You saw how to install the Calcium NuGet packages. You looked at creating a Xamarin Forms shared project, and at initializing your app with a Bootstrapper. You saw how create a basic page in XAML and how to bind that page to a viewmodel. Finally, you saw an overview of some of Calcium’s default services, including a Dialog Service, Settings Service, and a User Options Service.

In this second part of the series you see how to extend beyond current Xamarin Forms API capabilities to create a tabbed page or carousel page by binding to a collection of ViewModels. You see how to implement a quasi-data-template selector to materialize viewmodels using MVVM.

If you haven’t already, I recommend reading the first article in the series before tackling this one. 

Article in this Series

Source Code for Calcium and Example Apps

The source code for Calcium for Xamarin.Forms and the examples is located at https://calcium.codeplex.com/SourceControl/latest

There are various solutions throughout the repository. The one you are interested in is located in the \Trunk\Source\Calcium\Xamarin\Installation directory, and is named CalciumTemplates.Xamarin.sln.

The structure of the CalciumTemplates.Xamarin solution is shown in Figure 1. You can see the example 'template' projects have been highlighted. These are the projects that contain much of the example source code that is presented within this article series.

Solution Structure

Figure 1. Structure of CalciumTemplates.Xamarin Solution

Creating a Tabbed Page in Xamarin.Forms

TabbedPage provides a user friendly way to present content that is split across multiple pages yet presented in the same view. Let’s look at the conventional approach to creating a tabbed page in XAML. You do this by creating creating a new Forms XAML Page and changing the root node type to TabbedPage, as shown in the following excerpt:

XML
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"                                     
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="CalciumTemplateApp.Views.HubView">

       <TabbedPage.Children>
              <ContentPage />
              <ContentPage />
              <ContentPage />
       </TabbedPage.Children>

</TabbedPage>

Child pages are nested within the Children element of the TabbedPage element.

The downside with this approach is that it’s not all that flexible in that it requires you to know up front what’s going be placed in your TabbedPage.

TabbedPage also provides an ItemTemplate property that allows you to materialize a set of objects in a unified way, as shown:

XML
<TabbedPage.ItemTemplate>
       <DataTemplate>
              <ContentPage Title="{Binding Title}" />
       </DataTemplate>
</TabbedPage.ItemTemplate>

This is approach is useful if you have a set of objects of the same type that you wish to render in the same way. The drawback is that it doesn’t allow you to vary the manner in which objects are rendered; you have a single data template and that’s that.

A common requirement I’ve encountered is where you have a collection of objects of differerent types that need presenting in a different ways. Unfortunately Xamarin Forms does not yet have a Data Template Selector equivalent, which is present in WPF. In this section, however, I’ll demonstrate how to create a quasi template selector, which allows you to data-bind an ObservableList of viewmodels to a TabbedPage; bringing us ever-closer to that place of MVVM nirvana.

Let us now look at a concrete example.

The HubViewModel includes an ObservableCollection of viewmodels. This collection is populated with an AboutViewModel and an OptionsViewModel when the HubViewModel is created, as shown in the following excerpt:

C#
public HubViewModel() : base(AppResources.Views_Hub_Title)
{
    var optionsViewModel = Dependency.Resolve<OptionsViewModel>();
    ViewModels.Add(optionsViewModel);
    
    var aboutViewModel = Dependency.Resolve<AboutViewModel>();
    ViewModels.Add(aboutViewModel);
}

 

The HubView is a TabbedPage whose ItemsSource property is data bound to the viewmodel’s collection of child viewmodels, as shown in the following excerpt:

XML
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            x:Class="CalciumSampleApp.Views.HubView"
            xmlns:calcium="clr-namespace:Outcoder.UI.Xaml;assembly=Outcoder.Calcium"

            ItemsSource="{Binding ViewModels}">

       <TabbedPage.ItemTemplate>
              <DataTemplate>
                     <calcium:ViewLocatorPage Title="{Binding Title}" />
              </DataTemplate>
       </TabbedPage.ItemTemplate>

</TabbedPage>

The ViewLocatorPage, in the previous excerpt, derives from ContentPage and is tasked with populating itself with a different view depending on the value of its BindingContext property. It does this whenever it detects that its BindingContext property has changed by subscribing to its own BindingContextChanged event. See Listing 1.

You’ll notice that if the parent’s BindingContext is the same as the BindingContext of the page itself, then the change event is disregarded. This is because until the binding context of the page is explicitly assigned the object from the collection, it inherits the BindingContext of its parent. This is binding context inheritance, and it is performed for you by the SetInheritedBindingContext method of the TabbedPage’s base BindableObject class. But, I digress.

Returning to the HandleBindingContextChanged method, the usual motions of binding the view to the viewmodel proceeed, and the Content property of the ViewSelectorPage is switched to the resolved view. That’s essentially the data template selector step.

NOTE. Each view that is presented within the TabbedPage must derive from the View class.

I’ve added an extensibility point to the ViewLocatorPage, in the form of a ViewLocator property. Contained within the ViewLocator is the actual logic for locating a view.

Listing 1. ViewLocatorPage.HandleBindingContextChanged Method

C#
void HandleBindingContextChanged(object sender, EventArgs e)
{
       var context = BindingContext;                    

       if (context == null)
       {
              Content = null;
              return;
       }                    

       var parent = Parent;

       if (parent != null && parent.BindingContext == context)
       {
              /* The BindingContext may be set to the parent's context
               * before being set to a child viewmodel. */
              return;
       }

       IViewLocator locator = ViewLocator;

       if (locator == null)
       {
              ViewLocator = locator = this;
       }

       var newContent = locator.LocateView(BindingContext, Content);

       if (newContent != Content)
       {
              Content = newContent;
       }
}

The IViewLocator interface contains a single method named LocateView, which accepts the binding context object (the viewmodel) and the existing view that is being used to present the viewmodel. The LocateView method returns a Xamarin.Forms.View instance.

The default implementation of the IViewLocator is the ViewLocatorPage itself. It explicitly implements the interface, as shown in Listing 2. The view is located according to the naming convention that viewmodels are suffixed with Model and their view counterparts exist in the same assembly and in the same namespace. When the type of the view is able to be resolved, the object is built-up using the dependency system. The view is then returned to the ViewLocatorPage, which assigns it to its Content property.

Listing 2. ViewLocatorPage LocateView method

C#
View IViewLocator.LocateView(object bindingContext, View currentView)
{
       if (bindingContext == null)
       {
              return null;
       }


       Type bindingContextType = bindingContext.GetType();
       string typeName = bindingContextType.FullName;

       const string viewModelSuffix = "Model";

       if (typeName.EndsWith(viewModelSuffix))
       {
              string viewName = typeName.Substring(0, typeName.Length - viewModelSuffix.Length);

              var currentContent = currentView;

              if (currentContent != null && currentContent.GetType().FullName == viewName)
              {
                     return currentView;
              }

              var assembly = bindingContextType.Assembly;

              Type type = Type.GetType(viewName + ", " + assembly.FullName, false);

              if (type != null)
              {
                     var content = Dependency.Resolve(type);
                     View view = (View)content;
                     view.BindingContext = bindingContext;
                     view.BindToInfrastructure();

                     return view;
              }
       }

       return null;
}

You can change the way in which views are located by assigning a different IViewLocator object to the ViewLocatorPage’s ViewLocator property. Feel free to grab the code and modify to support whatever custom scenario you require. If it’s something interesting that you believe will be of benefit to others, let me know and I will incorporate it into the codebase.

Incorporating Bindable Properties

Xamarin Forms includes the equivalent of a WPF, Silverlight or WinRT DependencyProperty, but in Xamarin they go by the name of BindableProperty. They are equivalent in most respects. They are ordinarily defined as static members of a BindableObject, which in this example is the ViewLocatorPage.

NOTE. While DependencyProperty and BindableProperty types share many similarities there are some notable differences. One is that in Xamarin Forms it is not possible to retrieve a particular Binding object from a BindableObject, to rebind or examine the binding path. I’ve found this to be rather useful in WPF, Silverlight and WinRT.

The ViewLocator is defined like so:

C#
public static readonly BindableProperty ViewLocatorProperty;

The ViewLocatorPage’s static constructor initializes the BindableProperty as shown:

C#
static ViewLocatorPage()
{
       ViewLocatorProperty = BindableProperty.Create("ViewLocator",
              typeof(IViewLocator), typeof(ViewLocatorPage),
              null, BindingMode.TwoWay);
}

An instance property exposes the BindableProperty to consumers. The GetValue and SetValue methods will look familiar to experienced XAML developers. Both methods are inherited from the BindableObject base class. The following excerpt show the ViewLocator property of the ViewLocatorPage class:

C#
public IViewLocator ViewLocator
{
       get
       {
              return (IViewLocator)GetValue(ViewLocatorProperty);
       }
       set
       {
              SetValue(ViewLocatorProperty, value);
       }
}

This enables the ViewLocator to be data-bound to any other object.

NOTE. Restrictions placed on data bindings are the same as they are in WPF, Silverlight and WinRT. In Xamarin Forms the target of a data-binding (e.g., the viewmodel) can be any object, but the source must be a BindableObject. Remember that the source can be anything but the target must be a BindableObject.

The ViewSelectorPage is a ContentPage, and as such its Content property must resolve to a Xamarin.Forms.View object. Thus, the view presenting each ViewModel must derive from Xamarin.Forms.View. This is somewhat more limiting than the capabilities of even Windows Phone Silverlight, but it’s liveable. Though, if you plan on materializing your ContentViews outside of a MultiPage<T>, you’ll need to do some reengineering.

Figure 2 shows how a TabbedPage is rendered across the three platforms; Windows Phone, Android, and iOS.

Tabbed page displayed for all three platforms.

Figure 2. TabbedPage has three platform specific representations.

Applying quasi data-templates allows you to construct composite viewmodels that consist of sets of child viewmodels, and allows you to add new views from a composite viewmodel, to coordinate activity between views and to dynamically add and remove views within business logic.

Preventing Orientation Changes from Restarting Your Android Activity

Before ending this article, I wish to briefly draw your attention to something that is important when building Xamarin Forms apps on Android. By default, the current activity in an Android app is reset when the user changes the orientation of the device. This can play havic with you views and reset your layout to your main view. To prevent this, include a ConfigurationChanges attribute in the Activity attribute of your main activity, like so:

C#
[Activity(Label = "Calcium Example", MainLauncher = true,
              ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation
             | Android.Content.PM.ConfigChanges.ScreenSize)]
public class MainActivity : AndroidActivity
{
...
}

Conclusion

In this article you saw how to create a tabbed page or carousel page by binding to a collection of ViewModels. You learned how to implement a quasi-data-template selector to materialize viewmodels using MVVM.

In the next article, Part 3. Building Localized Cross-Platform Apps with Xamarin Forms and Calcium, you look at applying the same principles to display categorized lists of user options. Options can be added with a single line of code. Templates control how types of options are displayed. The options system works in unison with the settings service to automatically write changes back to persistent storage. It’s going to be fun, so I hope you’ll join me.

I hope you find this project useful. If so, then I'd appreciate it if you would rate it and/or leave feedback below. This will help me to make my next article better.

History

September 2014

  • Initial publication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)