This Xamarin how-to article shows (in my opinion) the easiest and cleanest way to implement the MVVM pattern in a DataTemplate. The example I will be using is a ‘set up wizard’ using a standard Xamarin.Forms CarouselView and IndicatorView. The example show how you can keep each ‘page’ in the setup wizard separate with a separate view model also even though they are all contained within the same navigation page.
The Main XAML Page
This is the simple XAML page code that will hold all of our individual pages/templates within a CaraouselView with indicator dots on the bottom of the page. It can be seen that we still need to create a ViewModel and a TemplateSelector which will be covered.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:MvvmDataTemplate" x:Class="MvvmDataTemplate.MainPage" Title="Setup Wizard"> <ContentPage.Resources> <ResourceDictionary> <local:WizardTemplateSelector x:Key="WizardTemplateSelector"/> </ResourceDictionary> </ContentPage.Resources> <ContentPage.BindingContext> <local:MainPageModel/> </ContentPage.BindingContext> <StackLayout Margin="20"> <CarouselView ItemsSource="{Binding Wizard}" HorizontalScrollBarVisibility="Never" IsSwipeEnabled="True" Loop="False" IndicatorView="indicatorView" ItemTemplate="{StaticResource WizardTemplateSelector}"/> <IndicatorView x:Name="indicatorView" IndicatorColor="LightGray" SelectedIndicatorColor="Gray" HorizontalOptions="Center"/> </StackLayout> </ContentPage> |
Templates & Template Models
Next we can create all of our DataTemplates that we want to use in our wizard, along with a ViewModel for each. The ViewModels will all be inheriting from a base ViewModel. The main thing that we need to remember is that when using bindings from our ViewModel in our DataTemplate, we need to prefix the property name with “Model.”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="UTF-8" ?> <DataTemplate xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MvvmDataTemplate.Templates.StepOneTemplate"> <StackLayout Spacing="20"> <Label Text="Step 1" HorizontalTextAlignment="Center"/> <Label Text="Welcome to the set up wizard of the Xamarin how-to app" HorizontalTextAlignment="Center"/> <Label Text="Tap the button below to see more information"/> <Button Text="Tap Me" Command="{Binding Model.ButtonCommand}"/> <Label Text="{Binding Model.Message}" IsVisible="{Binding Model.IsMessageShowing}"/> </StackLayout> </DataTemplate> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System; using System.ComponentModel; using System.Runtime.CompilerServices; namespace MvvmDataTemplate.TemplateModels { public class BaseTemplateModel : INotifyPropertyChanged { public BaseTemplateModel() { } protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (Equals(storage, value)) return false; storage = value; RaisePropertyChanged(propertyName); return true; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null) { var e = new PropertyChangedEventArgs(propertyName); PropertyChanged?.Invoke(this, e); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using System; using Xamarin.Forms; namespace MvvmDataTemplate.TemplateModels { public class StepOneTemplateModel : BaseTemplateModel { public StepOneTemplateModel() { } private bool _isMessageShowing; public bool IsMessageShowing { get => _isMessageShowing; set => SetProperty(ref _isMessageShowing, value); } public string Message => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; public Command ButtonCommand => new Command(() => IsMessageShowing = true); } } |
The Wizard Model
Now that you have seen an example of what a template looks like and the corresponding TemplateModel, you can now create the rest for the remainder wizard steps. We need to now create a Wizard model to create a list to use on the Main ViewModel. This will automatically retrieve the TemplateModel based on the enum.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using System; using MvvmDataTemplate.TemplateModels; namespace MvvmDataTemplate.Models { public class Wizard { public Wizard(WizardPage page) { Page = page; Model = GetModel(); } public WizardPage Page { get; set; } public BaseTemplateModel Model { get; set; } private BaseTemplateModel GetModel() { switch(Page) { case WizardPage.STEP_ONE: return new StepOneTemplateModel(); case WizardPage.STEP_TWO: return new StepTwoTemplateModel(); case WizardPage.STEP_THREE: return new StepThreeTemplateModel(); default: return new BaseTemplateModel(); } } } public enum WizardPage { STEP_ONE, STEP_TWO, STEP_THREE } } |
The Wizard Template Selector
Now that our Wizard model automatically retrieves the TemplateModel we will use a DataTemplateSelector to retrieve the correct page. By inheriting from the DataTemplateSelector from Xamarin.Forms we can automatically retrieve the Page/DataTemplate based on the enum as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using System; using MvvmDataTemplate.Models; using MvvmDataTemplate.Templates; using Xamarin.Forms; namespace MvvmDataTemplate { public class WizardTemplateSelector : DataTemplateSelector { private DataTemplate StepOneTemplate = new StepOneTemplate(); private DataTemplate StepTwoTemplate = new StepTwoTemplate(); private DataTemplate StepThreeTemplate = new StepThreeTemplate(); protected override DataTemplate OnSelectTemplate(object item, BindableObject container) { switch (((Wizard)item).Page) { case WizardPage.STEP_ONE: return StepOneTemplate; case WizardPage.STEP_TWO: return StepTwoTemplate; case WizardPage.STEP_THREE: return StepThreeTemplate; default: return new DataTemplate(); } } } } |
The Main ViewModel
Now that we have everything in place, we can finally create a List with all of our wizard pages and corresponding models.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System; using System.Collections.Generic; using MvvmDataTemplate.Models; namespace MvvmDataTemplate { public class MainPageModel { public MainPageModel() { var wizard = new List(); wizard.Add(new Wizard(WizardPage.STEP_ONE)); wizard.Add(new Wizard(WizardPage.STEP_TWO)); wizard.Add(new Wizard(WizardPage.STEP_THREE)); Wizard = new List(wizard); } public List Wizard { get; set; } } } |
That’s all for this time, you now have an easy and expandable pattern to use for DataTemplates so that we are able to keep everything clean and MVVM.
Full source code available here.
Leave A Comment