Thinking Out Loud: Events, Messaging and Mvvm Navigation with XAML Frameworks

This post will explores mvvm navigation further, employing the latest c# 9 code generator to reduce the boilerplate code that developers have to write.

In my previous post on this topic, Thinking Out Loud: Mvvm Navigation for XAML Frameworks such as Xamarin.Forms, UWP/WinUI, WPF and Uno, I explored using events emitted by a ViewModel to drive page navigation. This post will explore this concept further, employing the latest c# 9 code generator to reduce the boilerplate code that developers have to write.

Before we go on, let’s just recap of where we got to previously:

  • ViewModels are independent, not knowing what’s before or after them in the navigation flow of the application
  • Use events to signify when a ViewModel is complete
  • ViewModel events are converted to navigation methods at an application level

The upshot is that you can have a simple ViewModel that simply raises an event to indicate that it’s complete (for example when the user clicks a submit button on a form).

public class MainViewModel
{
    public event EventHandler ViewModelDone;
    public async Task<int> DoSomething()
    {
        var rnd = new Random().Next(1000);
        await Task.Delay(rnd);
        if (rnd % 2 == 0)
        {
            ViewModelDone?.Invoke(this, EventArgs.Empty);
        }
        ... 
    }
}

There were a couple of pieces of feedback following my previous post:

  • Use an Observable instead of an event
  • Bypass the ViewModel event completely and simply raise a message that could be handled by the application. For example a developer could attach a Behavior to a Button that would send a message to an application wide dispatcher that could determine where to navigate to based on the message type, or perhaps the parameter set.

Whilst I like the first idea of a ViewModel exposing an Observable, I think that this is an idea we’ll explore sometime in the future. Using an event is incredibly simple and gives us the clear separation we’re after. The only downside is that the add/remove handler code required for events is somewhat nasty.

The idea of using messages, and having a central dispatcher for messages, is a great idea and one that I wanted to explore. I didn’t want to change the ViewModel to have to emit a message, since again this just adds additional complexity to the ViewModel. This means that there needs to be some sort of conversion between events and messages. As you can imagine, this is simply adding more code that developers need to write in order to get everything to work.

I’ve just pointed out two areas where developers will have to write unnecessary code: add/remove event handlers and converting between events and messages. I’ll be using the c# 9 code generators to help eliminate this excess code.

TL;DR

In this post I’m not going to I walk through the complexities of events, messaging and code generation because that would make for a long post. Instead, let me walk through a scenario where we’re going to add a new page, FifthPage, to our existing application (which as you can probably guess, already has four pages). Here’s what we need:

  • Add FifthPage
  • Add a Button to MainPage that navigates to FifthPage when clicked
  • Add Button to FifthPage that navigates back to MainPage when clicked
  • Add FifthViewModel that will be the DataContext for FifthPage
  • Add string property, Title, to FifthViewModel that returns a page title
  • Add TextBlock to FifthPage that is bound to the Title property on the FifthViewModel.

Create Page and ViewModel

The first step is to create the FifthPage and FifthViewModel. I’ve separated out the application code into different projects, so I have my view models in a project called MvvmNavigation.Core. My pages are still in the MvvmNavigation.Shared project that was created by the Uno solution template.

Whilst we’re creating these classes, we’ll add some of the basics that we’re going to need. On the FifthPage, we’ll add a TextBlock, for the Title, and a Button, to trigger navigation back to the MainPage.

<Page
    x:Class="MvvmNavigation.FifthPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <StackPanel VerticalAlignment="Center"
                HorizontalAlignment="Center">
        <TextBlock Text="page title" />
        <Button Content="Go Back" />
    </StackPanel>
</Page>

In the FifthViewModel we’ll add a Title property and a simple event, FifthDone, that will indicate to the application that the FifthViewModel is done. The application will be responsible for determining how to handle this; which according to our specification, should navigate back to MainPage. We’ll also create a RaiseFifthDone method that can be called to invoke the FifthDone event.

public class FifthViewModel
{
    public event EventHandler FifthDone;

    public string Title => "Page 5";

    public void RaiseFifthDone()
    {
        FifthDone?.Invoke(this, EventArgs.Empty);
    }
}

Navigation to FifthPage

To navigate to the FifthPage we need a new Button on the MainPage that, when clicked, will raise a message, PleadTheFifthMessage, that the application will handle in order to navigate to the FifthPage. Let’s unpack this into the steps:

Add Button

Add a Button to the MainPage XAML, along with the NavigationMessageAction behavior which will raise the PleadTheFifthMessage

<Button Content="Go To Page 5">
    <Interactivity:Interaction.Behaviors>
        <Interactions:EventTriggerBehavior EventName="Click">
            <builditbehaviors:NavigationMessageAction MessageType="localmessages:PleadTheFifthMessage" />
        </Interactions:EventTriggerBehavior>
    </Interactivity:Interaction.Behaviors>
</Button>

PleadTheFifthMessage

Add a class, PleadTheFifthMessage, that inherits from CompletedMessage.

public class PleadTheFifthMessage : CompletedMessage
{
    public PleadTheFifthMessage() : base() { }
    public PleadTheFifthMessage(object sender) : base(sender) { }
}

Map PleadTheFifthMessage to FifthViewModel

As part of the MvvmApplicationService, we need to register a navigation to the FifthViewModel for the PleadTheFifthMessage. In this case, we’re only handling the PleadTheFifthMessage for when it’s raised by the MainViewModel.

serviceRegistrations.AddSingleton<INavigationMessageRoutes>(sp =>
{
    var routes = new NavigationMessageRoutes()
        .RegisterNavigate<MainViewModel, PleadTheFifthMessage, FifthViewModel>()
        .RegisterNavigate<MainViewModel, CompletedMessage, SecondViewModel>()
	// ... omitted for brevity
        .RegisterGoBack<CloseMessage>();

    return routes;
});

FifthPage – FifthViewModel Mapping

Whilst we’ve defined a mapping from the PleadTheFifthMessage to the FifthViewModel, there needs to be a way for the application to connect the FifthViewModel to the FifthPage. Rather than rely on naming convention, we’re going to apply an Attribute to the FifthPage.

[ViewModel(typeof(FifthViewModel))]
public sealed partial class FifthPage 
{
    public FifthPage()
    {
        this.InitializeComponent();
    }
}

ViewModel Binding

So far we’ve wired up the navigation from MainPage to FifthPage. However, when arriving at FifthPage it’s clear than neither the Title, nor the Button event handler, has been wired up. The title could be easily wired up by simply creating an instance of the FifthViewModel in XAML and setting it as the DataContext.

However, this doesn’t scale well for real world applications where a view model may be dependent on any number of services that are required (eg for fetching and/or saving data). It’s preferable to have some sort of depedency injection framework that can be used to instantiate the view model.

ViewModel Instantiation

To this end, we’re going to add a ViewModel property to our FifthPage, along with a partial method, InitiViewModel (note also that we’ve added a second parameter to the ViewModel attribute). The implementation of the partial method will be done by our code generator that will generate the code necessary to instantiate, along with any dependent services, the FifthViewModel.

[ViewModel(typeof(FifthViewModel), nameof(InitViewModel))]
public sealed partial class FifthPage 
{
    partial void InitViewModel();
    public FifthViewModel ViewModel => this.ViewModel(() => DataContext as FifthViewModel, () => InitViewModel());

    public FifthPage()
    {
        this.InitializeComponent();
    }
}

FifthViewModel Registration

Despite providing the mapping between FifthPage and FifthViewModel, there’s currently no way for the dependency injection container to create an instance of FifthViewModel, since we haven’t registered the FifthViewModel type. Rather than have the developer work out where to add the code to register the FifthViewModel type, we can simply attribute the FifthViewModel:

[Register]
public class FifthViewModel
{
    // ... 
}

Bind FifthViewModel With x:Bind

We can then update the XAML of our FifthPage to data bind both the Title property and the RaiseFifthDone method on the FifthViewModel.

<Page x:Class="MvvmNavigation.FifthPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <StackPanel VerticalAlignment="Center"
                HorizontalAlignment="Center">
        <TextBlock Text="{x:Bind ViewModel.Title}" />
        <Button Content="Go Back"
                Click="{x:Bind ViewModel.RaiseFifthDone}" />
    </StackPanel>
</Page>

Navigation Back to MainPage

When we created the FifthViewModel we already created the FifthDone event which will be invoked by the RaiseFifthDone method. However, clicking the Button on the FifthPage currently does nothing – actually it does indeed raise the FifthDone event but currently nothing is listening to that event.

Let’s add the EventMessage attribute to the FifthDone event. In this case the attribute references the existing CloseMessage which is the message that will be dispatched when the FifthDone event is raised.

[Register]
public class FifthViewModel
{
    [EventMessage(typeof(CloseMessage))]
    public event EventHandler FifthDone;

    // ...
}

We already have a handler for the CloseMessage for all pages that will simply close the current page.

That completes all the steps necessary to add a new page along with navigation too and from the page. It seems there’s a lot of steps, but actually there’s only minimal code required and it facilitates a high degree of separation between the elements of the application.

If you want to check out the code for this example app, feel free to check out the MvvmNavigation GitHub repo. Note that this is a work in progress and that there will most likely be quite a bit of refactoring over the coming weeks, after which I’ll post about more of the details on how the various mappings work and the code generation that’s used behind the scenes.

Thinking Out Loud: Mvvm Navigation for XAML Frameworks such as Xamarin.Forms, UWP/WinUI, WPF and Uno

One of the things that’s often given me pause for thought is the approach we take to navigation within applications. For the purpose of this post I’m going limit the scope to just XAML based applications (XF/Maui, UWP/WinUI/Uno, WPF). In all of these application platforms there is a built in capability to navigate between pages. … Continue reading “Thinking Out Loud: Mvvm Navigation for XAML Frameworks such as Xamarin.Forms, UWP/WinUI, WPF and Uno”

One of the things that’s often given me pause for thought is the approach we take to navigation within applications. For the purpose of this post I’m going limit the scope to just XAML based applications (XF/Maui, UWP/WinUI/Uno, WPF). In all of these application platforms there is a built in capability to navigate between pages. However, once we introduce view models, we want to be able to drive navigation from our view models. In this post I’m going to go through a bit of a thought journey to look at some of the existing strategies for view model navigation, discuss the pros and cons, and then look at whether we can build an alternative.

Rather than try to cover all the XAML platforms at every point along the way, I’m going to use a newly created @unoplatform application throughout this post. Towards the end we’ll look at whether we can adapt what we’ve created to the other XAML platforms – if we’ve done the job well, it should be a relatively easy thing to do.

Basic Page Navigation

As part of creating our project, MvvmNavigation, we’re given a MainPage, which is clearly the starting point of our application. To begin with, let’s:

  • Create another page, SecondPage
  • Add a Button to MainPage and an event handler that will simply navigate to SecondPage
  • Add a Button to SecondPage and an event handler to navigate back to MainPage
  • Add a TextBlock to both pages to give the pages a title

The XAML for MainPage

<Page x:Class="MvvmNavigation.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:MvvmNavigation"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="Main Page"
                   Margin="100"
                   FontSize="50"
                   HorizontalAlignment="Center" />
        <Button Content="Go to Second Page"
                Click="GoToSecondPageClick"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Center"
                Margin="100" />
    </Grid>
</Page>

The GoToSecondPageClick event handler in the codebehind of MainPage

private void GoToSecondPageClick(object sender, RoutedEventArgs e)
{
    Frame.Navigate(typeof(SecondPage));
}

The XAML for SecondPage

<Page x:Class="MvvmNavigation.SecondPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:MvvmNavigation.Shared"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="Second Page"
                   Margin="100"
                   FontSize="50"
                   HorizontalAlignment="Center" />
        <Button Content="Go back to Main Page"
                Click="GoBackClick"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Center"
                Margin="100" />
    </Grid>
</Page>

The GoBackClick event handler in the codebehind of SecondPage

private void GoBackClick(object sender, RoutedEventArgs e)
{
    Frame.GoBack();
}

And final a screenshot of each page that we’re navigating between.

What we can see from this example is that in a UWP/Uno application, navigating between pages is done by calling navigation methods on the Frame. As an aside, a UWP application can not only support multiple Windows, it can also support multiple frames. This can be very handy if you’re building complex desktop applications where you want to be able to control multiple page stacks. For the moment we’ll keep things simple and just consider a single window, single frame application.

ViewModel Navigation

Let’s augment our example by adding in some view models. To ensure we don’t pollute our view models with UI/framework specific code, we’ll add our view models to a separate .NET Standard class library, MvvmNavigation.Core. In this case I’ll create a view model for each page we currently have.

public class MainViewModel
{
    public string Title { get; } = "Main Page - VM";
}

public class SecondViewModel
{
    public string Title { get; } = "Second Page - VM";
}

One thing you’ll notice about a lot of mvvm frameworks is that they all seem to have their own base class. Where possible, I’m going to try to avoid this, in order to keep our view models as simple as possible. If you want to have bindings update the viewmodel will need to implement INotifyPropertyChanged, so having a base class with that implementation is something you’ll want to add. For the moment we’ll keep the view models as simple as possible.

Now let’s connect our view models to our pages. We’ll keep this really simple by creating the view models inline in the page as part of setting the DataContext. We’ll also update the Text on the TextBlock to bind to the Title property on the view model.

<Page x:Class="MvvmNavigation.MainPage" ... >
    <Page.DataContext>
        <vms:MainViewModel />
    </Page.DataContext>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{Binding Title}" ... />
    ...
</Page>

With the basic setup done, we can start to think about navigation. Firstly, let’s set the scene as to why the current navigation (i.e. controlled invoked in the code behind of the page) is a problem. In our MainViewModel, let’s say we have a method that does something, DoSomething.

public async Task<int> DoSomething()
{
    var rnd = new Random().Next(1000);
    await Task.Delay(rnd);
    if (rnd % 2 == 0)
    {
        // Do one thing
    }
    else
    {
        // Do something else
    }
    return rnd;
}

After delaying for a random amount of time, it decides to do one of two things. Let’s assume that if the random number is even, the app should navigate to SecondPage. However, currently the view model doesn’t have a way to do this. We could augment the return value to return a flag to indicate that it should navigate. Alternatively, we could move the decision logic into the code behind. I don’t like either of these options and it would be much clearer if the view model had a way to invoke navigation itself.

This leads us to a quick discussion on how this is currently implemented by different frameworks. Typically the solution is to pass in a service, call it something like INavigationService, into the constructor for the view model. When the view model wants to navigate it calls a method, such as Navigate, on the service. Depending on which framework it is, the parameter might be a url or it could be the type of view model to navigate to (i.e. navigate to the page that is associated with the specified view model type).

My issue with this approach is two fold. Firstly, we’ve added a dependency to the view model that doesn’t really assist the view model with what it needs to do (controlling the state for the view; loading and updating data that’s data bound to the UI etc). Secondly, we’ve added in an implicit linkable between the current view model and the view model that’s being navigated to.

If I had to pick between navigating using a url versus the type of a view model, my preference is the type of a view model, purely because this can be enforced with type safety. However, my point is that in both cases there’s an implied link that’s only visible if you walk the code of the view model.

Event Based Navigation

Instead of using an injected navigation service, what if we simply expose an event on the view model for when we need navigation to take place. For example, our MainViewModel could look like

public class MainViewModel
{
    public event EventHandler ViewModelDone;
    public async Task<int> DoSomething()
    {
        var rnd = new Random().Next(1000);
        await Task.Delay(rnd);
        if (rnd % 2 == 0)
        {
            ViewModelDone?.Invoke(this, EventArgs.Empty);
        }
        ... 
    }
}

Note that I didn’t call the event NavigateToSecondPage because this would again start to build in that implicit linkage between the view models/pages. The point is that the MainViewModel doesn’t, and shouldn’t, care about what’s going to happen next, it just knows that it’s time is up and it’s job is done.

The only thing now, is that we need something to listen to the event. For the moment this will be the MainPage:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    (DataContext as MainViewModel).ViewModelDone += MainViewModelDone;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    (DataContext as MainViewModel).ViewModelDone -= MainViewModelDone;
    base.OnNavigatedFrom(e);
}

private void MainViewModelDone(object sender, EventArgs e)
{
    Frame.Navigate(typeof(SecondPage));
}

private void GoToSecondPageClick(object sender, RoutedEventArgs e)
{
    (DataContext as MainViewModel)?.DoSomething();
}

Whilst we’ve kept our MainViewModel clear of any dependency/relationship with SecondPage or SecondViewModel, what we’ve ended up with is a bunch of wiring and navigation logic in our MainPage.

Abstracting Navigation Logic

Let’s step away from the specifics of how events and navigation logic works and think about what we want to achieve. Essentially we need a way of defining a set of behaviours such that when an event is raised on a particular object, the application will navigate to a particular page. So, for each behaviour we want to define, we need a way to declare the event that is going to be listened for and the action that should take place when that event is triggered.

The complexity in abstracting the navigation logic comes when you attempt to declare these behaviours at an application level. At a page level you can simply attach and detach event handlers, similar to what we’ve just done with MainPage in the OnNavigatedTo and OnNavigatedFrom methods. At an application level, you don’t have the instances of the pages until the user has navigated to them. As such, we’ll need to hook into the navigation event on the Frame and wire/unwire the event handlers.

Here’s a summary of the pieces we need:

  • Behaviour class – this will define the event to listen to and the action to execute
  • NavigationRoutes class – this will hold the list of behaviours for the application
  • OnNavigation method – this is the event handler for the Navigated event on the root frame of the application

Let me try to walk through these in a way that hopefully makes sense. We’ll start with the behaviour class.

public class Behaviour:IApplicationBehaviour
{
    public Action<object> Init { get; set; }
    public Action<object> Deinit { get; set; }

    private Behaviour() { }

    public static Behaviour Create<T, THandler>(Action<T, THandler> init, Action<T, THandler> deinit, THandler action)
    {
        return new Behaviour()
        {
            Init = obj => init((T)obj, action),
            Deinit = obj => deinit((T)obj, action)
        };
    }
}

public interface IApplicationBehaviour
{
    Action<object> Init { get; set; }
    Action<object> Deinit { get; set; }
}

According to what I said earlier, the behaviour class needs to define an event and an action, yet the implementation doesn’t really look like it has either. It does, they’re just masked a little.

Firstly, the challenge with an event in C# is that you can’t easily just pass the event object around and attach/detach from it. We could have just switched it out for an Action or some other delegate but that would make the programming model for the view model just a bit weird. Alternatively we could have used some hocky reflection to get the job done but … well just, no… I don’t want to be doing reflection due to the inherit overhead and potential linker issues it might create.

Instead of passing an event object around, we instead pass in an Action for wiring and unwiring the event. For example the wiring action would look something like (vm, act) => vm.ViewModelDone += act.

Next, we need the action that’s going to be invoked. Well this is easily identifiable in the Create method as the third parameter but then what do we do with it? Rather than hold a direct reference to the action, we simply combine it with the init and deinit Action delegates and assign them to the public Init and Deinit methods.

Initially this looks a bit strange as the signature on the Init and Deinit methods are Action<object> instead of Action<T>, and then we’re assigning a delegate that coerces the object into T – why not just make them Action<T>? The answer to this comes if you look at the interface IApplicationBehaviour that defines Init and Deinit methods, both as Action<object>. As you’ll see shortly, we’re going to have a collection of these behaviours defined for a variety of different pages (i.e. T). Rather than attempt to keep these as strongly typed behaviours, we’re simply going to use an interface which doesn’t include the type argument.

You might be wondering whether the type coercion is safe? Well the short answer is no, it might not be. However, as you’ll see shortly, one of the reasons why we don’t expose either a public constructor on the Behaviour class, nor expose the collection of Behaviours directly is to ensure that this type coercion is always valid.

Next up is the NavigationRoutes class, which is essentially just a wrapper around a collection of Behaviour instances.

public class NavigationRoutes
{
    private IList<Tuple<Type, IApplicationBehaviour>> Behaviours { get; } = new List<Tuple<Type, IApplicationBehaviour>>();

    public NavigationRoutes Register<T, THandler>(Action<T, THandler> init, Action<T, THandler> deinit, THandler action)
    {
        Behaviours.Add(new Tuple<Type, IApplicationBehaviour>(typeof(T), Behaviour.Create(init, deinit, action)));
        return this;
    }

    public void Wire(object page)
    {
        var typeOfPage = page.GetType();
        var behaviours = Behaviours.Where(x => x.Item1 == typeOfPage).Select(x=>x.Item2);
        foreach (var behaviour in behaviours)
        {
            behaviour.Init(page);
        }
    }

    public void Unwire(object page)
    {
        var typeOfPage = page.GetType();
        var behaviours = Behaviours.Where(x => x.Item1 == typeOfPage).Select(x => x.Item2);
        foreach (var behaviour in behaviours)
        {
            behaviour.Deinit(page);
        }
    }
}

You might be wondering why we didn’t just use a Dictionary. The answer is so that we can support multiple Behaviour definitions per page. I’m also certain there are more optimal ways to structure the Wire and Unwire methods but they’ll do for what we need right now. They simply iterate through the collection of behaviours and invoke the Init or Deinit method on behaviours that are registered for the specified page.

We also need to define an instance of the NavigationRoutes class, which for the moment we’ll place in App.xam.cs.

public NavigationRoutes Routes { get; } = new NavigationRoutes()
    .Register<MainViewModel, EventHandler>(
                (vm, act) => vm.ViewModelDone += act,
                (vm, act) => vm.ViewModelDone -= act,
                (s, e) => (Window.Current.Content as Frame).Navigate(typeof(SecondPage)))
    .Register<SecondViewModel, EventHandler>(
                (vm, act) => vm.ViewModelDone += act,
                (vm, act) => vm.ViewModelDone -= act,
                (s, e) => (Window.Current.Content as Frame).GoBack());

As you can see from this code we’re registering the ViewModelDone on both MainViewModel and SecondViewModel. For the MainViewModel we’re registering an Action that navigates to SecondPage. For the SecondViewModel we’re registering an Action that calls GoBack on the Frame.

The last thing to do is to intercept the Navigated event on the Frame in order to wire/unwire the behaviours.

private object PreviousPage { get; set; }
private void OnNavigated(object sender, NavigationEventArgs e)
{
    if (PreviousPage != null)
    {
        Routes.Unwire((PreviousPage as Page).DataContext);
        PreviousPage = null;
    }

    PreviousPage = e.Content;
    Routes.Wire((PreviousPage as Page).DataContext);

}

Here you can see why we needed the Init and Deinit methods on the IApplicationBehaviour interface to be Action<object> – The Content property on the NavigationEventArgs is simply an object which we are casting it to a Page and extracting the DataContext, which is also untyped. Note that again, this is very rough at this point and obviously needs some null checking before it would be considered fit for use in production.

Testing Navigation Logic

Whilst we’ve successfully abstracted the navigation logic away from the view models, it’s still very closely tied to platform implementation. By this I mean that the actions we’ve registered rely on having a reference to the Frame and they specify the actual navigation action to carry out eg Navigate(typeof(MainPage)) or GoBack. If we wanted to write tests to validate our navigation logic, we can’t easily, unless they were UI tests that literally tested the behaviour of the application (I’m not saying these aren’t important, they’re just not something we can write as unit tests).

The next step in our journey is to put in place an abstraction of what we mean by navigation so that we can replace the Navigate and GoBack method calls with something that can be tested. If we look to existing mvvm frameworks for suggestions, we’ll remember that they typically pass a navigation service into the viewmodels. What if we do something similar – we create a navigation service interface that can be passed into our Actions. Of course, this will then require a per-platform implementation for how each navigation method should be carried out.

This sounds simply but the first issue we’ll come up against is that the Navigate method for UWP takes a type parameter being the type of the page to navigate to. If we’re abstracting navigation away from the UI platform, we need to define navigation based on types that aren’t associated with any UI platform (i.e. not MainPage or SecondPage). The logical conclusion is that in order to navigate to SecondPage, we should simply attempt to navigate to SecondViewModel and let the platform implementation worry about how to translate this to SecondPage.

Let’s start by defining the interface for our INavigationService. At this stage I’m not going to place any type constraints on the TViewModel since one of our goals is to avoid imposing a particular base class or interface on the view models we define.

public interface INavigationService
{
    Task Navigate<TViewModel>();
    Task GoBack();
}

Next up, the implementation of the NavigationService for Windows (UWP/Uno).

public class WindowsNavigationService:INavigationService
{
    private Frame NavigationFrame { get; }
    private NavigationRoutes Routes { get; }
    public IDictionary<Type, Type> ViewModelToPageMap { get; } = new Dictionary<Type, Type>();

    public WindowsNavigationService(Frame navigationFrame, NavigationRoutes routes)
    {
        NavigationFrame = navigationFrame;
        NavigationFrame.Navigated += OnNavigated;
        Routes = routes;
    }

    public async Task GoBack()
    {
        NavigationFrame.GoBack();
    }

    public async Task Navigate<TViewModel>()
    {
        NavigationFrame.Navigate(ViewModelToPageMap[typeof(TViewModel)]);
    }

    public WindowsNavigationService RegisterForNavigation<TPage, TViewModel>() where TPage : Page
    {
        ViewModelToPageMap[typeof(TViewModel)] = typeof(TPage);
        return this;
    }

    private object PreviousPage { get; set; }
    private void OnNavigated(object sender, NavigationEventArgs e)
    {
        if (PreviousPage != null)
        {
            Routes.Unwire((PreviousPage as Page).DataContext);
            PreviousPage = null;
        }

        PreviousPage = e.Content;
        Routes.Wire(this,(PreviousPage as Page).DataContext);
    }
}

There’s three main sections to the implementation. Firstly, as you’d expect the constructor is expecting a Frame, which will be used to implement the Navigate and GoBack methods. In order to call Navigate on the Frame there is a translation between the TViewModel type parameter on the Navigate method to the type of Page to navigate to. This is done by looking up the ViewModelToPageMap dictionary. This leads us to the second section, which is the RegisterForNavigation method that simply adds a mapping into the ViewModelToPageMap dictionary. It might seem weird that we’re returning the instance of the WindowsNavigationService but this is simply to allow for a more fluent way of calling the RegisterForNavigation method, which we’ll see shortly.

The last section of the implementation is actually code we’ve seen before, which simply wires and unwires the behaviours as the Frame navigates between pages. This was previously in the App.xaml.cs but makes sense to be incorporated into the WindowsNavigationServices.

In the App.xaml.cs, rather than attaching an event handler to the Navigated event on the rootFrame, we’re instead going to create an instance of the WindowsNavigationService and call the RegisterForNavigation method for each page/view model pair we have.

rootFrame = new Frame();

rootFrame.NavigationFailed += OnNavigationFailed;
var navService = new WindowsNavigationService(rootFrame, Routes)
    .RegisterForNavigation<MainPage, MainViewModel>()
    .RegisterForNavigation<SecondPage, SecondViewModel>();

The last thing we need to do is to change the implementation of the NavigationRoutes class in order to leverage the INavigationService. So far as part of registering each behaviour we’ve avoided placing any constraints on the event or the action that’s passed into the Register method. The type of the event in the init and deinit methods (i.e. THandler) simply has to match the type of the Action. The challenge with wanting to pass in the INavigationService is that we risk enforcing some sort of constraint on THandler. For example we could require THandler to inherit, or be an instance of, EventHandler<INavigationService>. If we did this, all of a sudden we’re leaking the concept of navigation back into our view model.

The work around for this is that instead of passing in an Action into the register method, we’re going to pass a function that accepts an INavigationService and returns an Action. Essentially this is a sneaky way for us to wrap an instance of the INavigationService in a way that makes it accessible to the Action, without affecting the signature of the Action itself. You can see from the following code that the signature of the event and Action is still EventHandler.

private NavigationRoutes Routes { get; } = new NavigationRoutes()
    .Register<MainViewModel, EventHandler>(
                (vm, act) => vm.ViewModelDone += act,
                (vm, act) => vm.ViewModelDone -= act,
                (nav) => (s, e) => nav.Navigate<SecondViewModel>())
    .Register<SecondViewModel, EventHandler>(
                (vm, act) => vm.ViewModelDone += act,
                (vm, act) => vm.ViewModelDone -= act,
                (nav) => (s, e) => nav.GoBack());

Now that I’ve given the endgame away, let’s take a quick look at the implementation. Firstly, the Behaviour implementation has changed. Due to an increase in complexity, it now makes sense to capture the init, deinit and actionFactory parameters. The public interface for IApplicationBehaviour has also been updated.

public class Behaviour<T, THandler> :IApplicationBehaviour
{
    private Action<T, THandler> Init { get; set; }
    private Action<T, THandler> Deinit { get; set; }

    private Func<INavigationService, THandler> ActionFactory { get; set; }

    private T attachedEntity;
    private THandler action;

    public void Attach(INavigationService navService, object entity)
    {
        if(attachedEntity!=null)
        {
            Detach();
        }

        action = ActionFactory(navService);
        attachedEntity = (T)entity;
        Init(attachedEntity, action);
    }

    public void Detach()
    {
        if (attachedEntity == null)
        {
            return;
        }

        Deinit(attachedEntity, action);
    }

    private Behaviour() { }

    public static Behaviour<T, THandler> Create(Action<T, THandler> init, Action<T, THandler> deinit,Func<INavigationService, THandler> actionFactory)
    {
        return new Behaviour<T, THandler>()
        {
            Init = init,
            Deinit = deinit,
            ActionFactory=actionFactory
        };
    }
}

public interface IApplicationBehaviour
{
    void Attach(INavigationService navService, object entity);
    void Detach();
}

Then there’s the implementation of NavigationRoutes

public class NavigationRoutes
{
    private IList<Tuple<Type, IApplicationBehaviour>> Behaviours { get; } = new List<Tuple<Type, IApplicationBehaviour>>();

    public NavigationRoutes Register<T, THandler>(Action<T, THandler> init, Action<T, THandler> deinit, Func<INavigationService,THandler> actionFactory)
    {
        Behaviours.Add(new Tuple<Type, IApplicationBehaviour>(typeof(T), Behaviour<T,THandler>.Create(init, deinit, actionFactory)));
        return this;
    }

    public void Wire(INavigationService navigationService, object page)
    {
        var typeOfPage = page.GetType();
        var behaviours = Behaviours.Where(x => x.Item1 == typeOfPage).Select(x=>x.Item2);
        foreach (var behaviour in behaviours)
        {
            behaviour.Attach(navigationService, page);
        }
    }

    public void Unwire(object page)
    {
        var typeOfPage = page.GetType();
        var behaviours = Behaviours.Where(x => x.Item1 == typeOfPage).Select(x => x.Item2);
        foreach (var behaviour in behaviours)
        {
            behaviour.Detach();
        }
    }
}

The main change here is really the Register method signature and that we’re calling Attach and Detach instead of Init and Deinit.

Ok, so we have the basics of a platform agnostic navigation protocol (I’m trying to avoid the overloaded use of framework – what we’ve built is just a couple of classes that help abstract navigation, it’s definitely not a framework…. just yet).

At the beginning I stated that we’d look at the implementation for other platforms but rather than draw this post out any further, let’s list a couple of things for a future post that we need to look at:

  • Implementation of INavigationService for Xamarin.Forms, WPF and WinUI (UWP and/or Desktop)
  • Passing parameters as part of navigation
  • Returning values as part of navigating back
  • Multi-window and potentially multi-region support

Would love feedback on this – check out the repo on github: https://github.com/nickrandolph/mvvmnavigation

Don’t Judge XAML Based On Lines of Code

For those following the on-going discussion around the future of XAML and specifically the use of XAML in DotNetMaui/Xamarin.Forms, this post is a follow on from two great posts discussing the options that are, or will be, available for Xamarin.Forms developers as we move forward with DotNetMaui: Mobile Blazor Bindings – Getting Started + Why … Continue reading “Don’t Judge XAML Based On Lines of Code”

For those following the on-going discussion around the future of XAML and specifically the use of XAML in DotNetMaui/Xamarin.Forms, this post is a follow on from two great posts discussing the options that are, or will be, available for Xamarin.Forms developers as we move forward with DotNetMaui:

I’ve already discussed why I feel that DotNetMaui will extend Xamarin.Forms to be an inclusive platform for developers wanting to build cross platform applications. Recent additions to Xamarin.Forms, such as C# markup, coupled with the features outlined on the DotNetMaui roadmap, suggest that the path forward will be full of options for developers.

This bring me to the point of this post, which is to discuss XAML both in the context of DotNetMaui/Xamarin.Forms but also in the context of UWP/WinUI/Uno. To do this I’m going to re-visit the scenario discussed by Dylan and Vincent and look at some alternatives that might make XAML a bit more appealing

Xamarin.Forms Visual States

When I initial read through Dylan’s post and reviewed the XAML I was initially shocked at how hard it was to read and in fact it took me a while to realise why it looked so chaotic and hard to follow. It came down to a couple of things:

  • Changing XAML attribute values based on data triggers at various points in the XAML
  • The lack of Visual States
  • Using MultiTrigger to change attribute values based on multiple data values.

Just so that I’m clear, I’m not saying that the code was poorly written; I’m just observing why I found it hard to read. Let me present an alternative XAML for the page and discuss how it makes the code a little more readable.

<?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:XamlFlags"
    x:Class="XamlFlags.MainPage"
    x:Name="Page">
    <ContentPage.BindingContext>
        <local:MainPageViewModel />
    </ContentPage.BindingContext>
    <StackLayout
        BindableLayout.ItemsSource="{Binding Options}">
        <BindableLayout.ItemTemplate>
            <DataTemplate>
                <Frame
                    CornerRadius="4"
                    Padding="0">
                    <StackLayout
                        Orientation="Horizontal"
                        Padding="5"
                        BackgroundColor="White">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState
                    x:Name="IsEnabledAndSelected">
                    <VisualState.StateTriggers>
                        <StateTrigger
                            IsActive="{Binding Selected}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter
                            Property="BackgroundColor"
                            Value="DarkBlue" />
                        <Setter
                            Property="IsVisible"
                            Value="False"
                            TargetName="OptionsSelectedLabel" />
                        <Setter
                            Property="Label.TextColor"
                            Value="White"
                            TargetName="OptionsValueLabel" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState
                    x:Name="IsNotEnabled">
                    <VisualState.StateTriggers>
                        <StateTrigger
                            IsActive="{Binding IsNotEnabled}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter
                            Property="IsEnabled"
                            Value="false" />
                        <Setter
                            Property="BackgroundColor"
                            Value="DarkGray" />
                        <Setter
                            Property="Label.TextColor"
                            Value="LightGray"
                            TargetName="OptionsValueLabel" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
                        <Button
                            Text="Select"
                            Command="{Binding BindingContext.SelectTypeCommand, Source={x:Reference Page}}"
                            CommandParameter="{Binding .}" />
                        <Label
                            Text="✓"
                            TextColor="White"
                            x:Name="OptionsSelectedLabel"
                            FontSize="12"
                            HorizontalOptions="EndAndExpand"
                            VerticalOptions="Center"
                            IsVisible="false" />
                        <Label
                            Text="{Binding Value}"
                            TextColor="Black"
                            x:Name="OptionsValueLabel"
                            HorizontalOptions="EndAndExpand"
                            VerticalOptions="Center" />
                    </StackLayout>
                </Frame>
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </StackLayout>
</ContentPage>

On initial inspection you might be looking at this XAML wondering whether it’s improved the readability. However, if you collapse the VisualStateManager.VisualStateGroups node in the editor, the code immediately looks much more readable, as shown in the following image.

We can clearly make out that the interface is made up of a stack of items with each row being made up off a Button and two Label elements. The default state is for the row to have a White background and for the ✓ Label to be hidden (IsVisible set to false).

Now, let’s look at the contents of the VisualStateManager.VisualStateGroups. In this case it defines a single VisualStateGroup in which I’ve included two VisualState elements. If we consider each row it can be in one of three states:

  • Enabled and Not Selected – This is the default state where the item hasn’t been selected (IsEnabled = true, IsSelected = false).
  • Enabled and Selected – This is the state where the item has been selected by the user clicking the Select button (IsEnabled = true, IsSelected = true).
  • Is Not Enabled – When an item in the list is selected, other items may be set to disabled (IsEnabled = false, IsSelected = false).

Even though there are only two VisualStates defined in the XAML, there are in fact three visual states because there is the default state. Each of the VisualState uses a StateTrigger to determine whether the visual state is active or not. Rather than attempting to data bind to two different properties on the underlying data model, I’ve explicitly created single properties that reflect whether the state is active or not. The default state is both the initial state of each row, as well as the state that is returned to if neither of the defined VisualStates are active (ie the StateTrigger IsActive is set to true).

One of the benefits of using visual states is that you can set multiple properties, on one or more different elements, essentially grouping all the changes required for a particular visual states. This eliminates the need to have data triggers littered through the XAML used to define the layout for the page. In this scenario each of the VisualState elements has multiple Setters defining the background colour, the foreground (text) colour and in the case of the IsEnabledAndSelected state, the visibility of the ✓ Label.

As I pointed out earlier, for me, this makes the XAML easier to read. I know other developers prefer to have the data triggers local to where the attributes being changed are in the XAML. This approach has some advantages, particularly where you only have a small number of changes in a large XAML document.

Before we move on from discussing Xamarin.Forms I would point out that the VisualStateManager is a relatively new addition and specifically the ability to use TargetName to target different elements from a single VisualState. I would encourage revisiting your XAML to see whether using the VisualStateManager will simplify your code. Alternatively, you may want to consider breaking your code up into different controls that will help encapsulate things like visual states whilst reducing the complexity of the XAML on each page. We’ll talk more about using UserControls in the context of UWP shortly but the same concept can be used for Xamarin.Forms as well.

UWP / WindowsUI / Uno

The XAML used by the Universal Windows Platform (ie UWP) provides a couple of alternatives when it comes to defining the desired layout. In this section we’re going to consider three options: Optimised, UserControl and ContainerStyle (Code is available on GitHub)

Optimised for Lines of Code

In this approach the goal was simply to reduce the number of lines of code. I’m not going to spend much time on this because I fundamentally think measuring the quality of a technology based on the number of lines of code is just ridiculous. The following image shows the resulting 41 lines of XAML – of course this is also contingent on my Visual Studio settings where the first attribute is on the same line as the element; other developers have different options set, so will see different numbers of lines.

I will comment on a couple of things:

  • Unlike the Xamarin.Forms approach that uses a horizontal StackLayout, this XAML uses a Grid which clearly defines three columns. I prefer this approach as it clearly defines the sizing option for each column. Using a StackLayout with different HorizontalOptions isn’t as clear in my opinion.
  • This code uses x:Bind instead of the usual Binding syntax. This allows for code to be generated during compilation that enforces type checking and results in better performance as it reduces the reliance on reflection.
  • Rather than using converters this code uses static methods defined on the page to convert property values (in some cases multiple properties) into the required attribute value. In practice I would not recommend doing this as it embeds logic in your XAML layout

UserControl with Visual States

In the previous section we optimised the XAML by interleaving logic into our XAML layout. This is a practice that I actively try to avoid when defining the XAML for a page or control. I try to ensure the XAML I create is as declarative as possible, which means avoiding calling methods as part of binding expressions. I’d even go so far as to say that you should avoid using converters unless they are simple type converters (the most obvious one being for bool to Visibility conversion). I advocate for all logic to be encapsulated in either the ViewModel (if UI relate logic) or in the Model (if business logic), thus making it testable and ensuring a clean separation from the way that it’s presented (i.e. the View).

Let’s look at how we can keep our XAML clean of logic by following a similar approach to what we did earlier for Xamarin.Forms. What’s interesting about UWP is that if you attempt to follow the same strategy of defining visual states within the DataTemplate you’ll find that it doesn’t work. Instead you either need to define the visual states within a UserControl, or you have to apply the visual states in the ItemContainerStyle.

We’ll start with defining a UserControl which will include the layout for each item in the list, along with the different visual states. Rather than just show you the code, let’s jump into Blend and use the visual designer to build the desired layout. Rather than boring you with creating the project and adding things like the MainViewModel, I’m just going to focus on creating the UserControl.

From Solution Explorer, right-click on the project or a sub-folder and select Add, New Item.

From the Add New Item dialog, select User Control, give it a name (eg OptionItemControl) and click Add. This should give you a new design surface that we can start adding controls to.

Using the Assets tool window, double-click on the Button and then twice on the TextBlock. Alternatively you can drag these controls onto the design surface.

If you look at the XAML you’ll notice that Blend has added some default attributes to each of the controls that you added. Normally my recommendation would be to tidy up whatever XAML Blend creates, as you go, to ensure your XAML remains clean and does exactly what you want. If you’re not that familiar with XAML, reviewing the XAML that Blend generates is a great way to learn about features that you might not be aware of.

If we look at the design surface we can see that the three controls we added are incorrectly located on the design surface – we’re after a single row with three cells that should hold the “select” Button, a check mark TextBlock and the option value TextBlock.

Let’s go ahead and create the cells in the Grid by defining three columns. We can do this using the design by hovering the mouse near the top of the design surface. The cursor should change to include a small + sign and if you click with the mouse a new column break will be added. You can then use the designer to specify how the column widths are defined. In this case we want the first column to be Auto and the other two columns to be * (just *, so you may have remove any numeric value added by Blend eg 101* should be changed to just *)

Next we want to reposition the two TextBlock into the appropriate column. You can do this by simply dragging them using the mouse. As you drag and hover over each cell in the Grid you’ll see alignment and positioning lines appear in red.

After positioning the TextBlock into the correct column you will want to tidy up the XAML – Blend has an annoying habit of adding Margins and other layout attributes that are typically unnecessary. You can clean up these excess attributes by right-clicking the control and selecting Layout, Reset All.

The next thing to do is to replace the default Text value for the TextBlock from “TextBlock” to something more meaningful. Rather than having to locate the Text property in the properties tool window, you can simply double-click the TextBlock and type directly on the designer. You can do the same thing with the Button control to change the Content to “Select”.

According to the design we’re aiming to achieve we need to change the default (i.e. Enabled but not Selected) Background to White. Use the colour picker in the Properties tool window to do this. This of course will override the theming and thus break support for dark and light themes, but that’s a topic for another day.

Unfortunately setting the Background to White means we can no longer see the three controls, since their default text colour is currently also White. This can be fixed easily by selecting all three controls in the Objects and Timelines tool window.

From the Properties tool window, update the Foreground colour to Black.

You’ll also need to adjust the BorderBrush on the Button. After doing this the layout should be similar to the following.

And the XAML for this is quite tidy.

What’s missing are the visual states for when the option is selected and when it’s disabled. We’ll add two VisualStates to the UserControl via the States window. Normally I advocate for creating a third VisualState that represents the default state – this is so that in code you can use the VisualStateManager GoToState method to return to the default layout. However, we’re going to be using state triggers to transition between states, so a third VisualState is not required – when none of the state triggers are set to active, the UserControl will return to the default state.

By clicking on a VisualState in the States tool window we can put Blend into state editing mode. This is highlighted by both the red dot alongside the VisualState, as well as the red border around the design surface. Any changes you make whilst this highlighting is visible will be recorded against the corresponding VisualState.

I’m not going to go through setting all the properties for each VisualState but essentially we need to update the Background colour on the Grid, the Foreground colour on the TextBlock and Button, and the Visibility on the checkmark TextBlock. For the checkmark TextBlock, you’ll need to switch the default Visibility to Collapsed – we had it visible whilst designing the layout but it’s default state is actually to be hidden (i.e. Collapsed).

As part of defining the VisualStates you may have noticed that the controls in the Objects and Timeline tool window were all given default names like grid, button, textBlock and textBlock1. I would encourage you to give these more meaningful names by double-clicking on them in the Objects and Timeline tool window and typing a new name.

Currently we have completed the design of the UserControl, including the three different states it can be in. However, we haven’t wired it up to any data. If we were to use this UserControl as it is, it would simply show a list of “Option 1”. So that we can wire up some data, let’s add an instance of the OptionViewModel as a design time DataContext for the UserControl (note the use of the d: prefix to indicate design time only).

Select the TextBlock that should show the OptionViewModel Value property. Click on the small square to the right of the Text property in the Properties tool window and select Create Data Binding.

Since we’ve set up an instance of the OptionViewModel as a design time DataContext, Blend is able to offer suggestions for the Path attribute. Select the Value property and click Ok.

At this point you’ll be wondering why the TextBlock that you just setup data binding for as disappeared from the designer. Well, the good news is that the TextBlock is still there, it just has zero width because the Value property on the OptionViewModel is returning null. This is easily fixed by specifying the Value property on the OptionViewModel specified in the design time DataContext.

Ok, we’re almost there. We have our layout, our Visual States and our data. We still need to be able to trigger then changing of states. For this we’re going to use a state trigger. Click the “Edit Adaptive Triggers” button next to the VisualState.

From the Dropdown at the bottom of the screen select <Other Type…>

In our project we have a trigger called DualBooleanDataTrigger which will allow us to trigger a VisualState based on the value of two different bool properties.

Unfortunately this is about as far as the data trigger design experience can take us. Luckily the XAML for the DualBooleanDataTrigger isn’t that complex. As you can see from this image, we need to data bind the DataValue and SecondDataValue attributes to the IsEnabled and IsSelected properties on the OptionViewModel and then we need to specify the value for each of these properties that we want the VisualState to be active for. In this case we want both the IsEnabled and IsSelected properties to be True for this state to be active.

The last thing we need to do is to wire up the Button to a method that will set the OptionViewModel to be selected. For this, we’re going to use x:Bind so that we can bind directly to a method on the OptionViewModel. In order to do this, we need to expose the the OptionViewModel as a property on the UserControl.

In the MainPage (i.e. where we’re defining the ItemsControl) we need to include an instance of the OptionItemControl in the DataTemplate. In order for x:Bind to work we need to make sure we set the ViewModel to be the current DataContext of the DataTemplate (i.e. binding path of . which can be omitted in this scenario).

We then need to specify the Click attribute on the Button.

After completing this we’re done! But let’s take a look at what’s been generated. Well, the good news is that we’ve created the entire user experience without having to write much XAML at all. The bad news is that Blend does a terrible job of generating optimized XAML. Take for example the following snippet where you can see that each Setter is spread over 5 lines.

In contrast, when I tidy these up, each Setter takes only 2 lines and that’s only because I have Visual Studio and Blend set to put attributes on new lines.

As you can see from this walk through, you can use a separate UserControl to easily design the layout for the three states required in this design.

ListView ItemsContainerStyle

The third option I wanted to briefly discuss (because there’s plenty of room for an entire discussion on this topic) is to override the ItemsContainerStyle for a ListView in order to define the VisualStates for the items in the list. Let’s back up for a second and explain a few things.

The scenario that we’re addressing is a common enough scenario where items in a list are selected. In this case the individual item tracks whether it’s selected (and in fact whether it’s enabled). This approach is actually at odds with the way a number of controls work. Take for example the ListView or GridView which are used to represent a list, or grid, of selectable items. Theses controls have built in styles that represent how each ListViewItem will look in various states. These states include (not an exhaustive list) Selected, Pressed, Enabled and Disabled. Further more, these control separate the appearance of each item (specified using the ItemTemplate) from the behaviour when the user interacts with the items (specified in the ControlTemplate used to define the ListViewItem Template nested in the ItemsContainerStyle). Both the ItemTemplate and ItemsContainerStyle are editable using the visual designer, allowing you to customise the appearance of the item and how it changes when the item is selected or enabled/disabled.

For example in Blend we can right-click on a ListView and select Edit Additional Templates, Edit Generated Item Container (ItemContainerStyle) and then either Edit Current, Edit Copy or Create Empty. I would suggest starting by selecting Edit Copy, which will give you a default style that you can customise, rather than starting from scratch.

In the following image you can see how I’ve defined different VisualStates for the ListViewItem to control the appearance for the three states the item can be in. You can also see all the other states that are available by default on the ListViewItem.

At this point you might be asking why, since the ListViewItem already has a Selected and a Disabled state, don’t I just use these. Well, the issue we have is that the Background and Foreground properties have three different colours based on the combination of two different properties (IsEnabled and IsSelected). This means we need to define states that are in the same group that respond to changes in both these properties. This is not how the built in VisualStates are defined. The Selected and Disabled states are in different VisualStateGroups – if we were to use these to control the same properties, we’ll end up with random and chaotic changes in the appearance.

VisualStates in different VisualStateGroups should not control the same property.

This is an important rule and one that’s easily forgotten or broken. If you ever see unusual behaviour relating to visual states, make sure you check this rule.

I’m not going to delve further into how to use the ItemsContainerStyle in this post but feel free to check out the source code. You’ll notice that this is very verbose coming in at around 130 lines, and this is using a much simplified ItemsContainerStyle. Unfortunately due to the complexity of the built in styles, the ItemsContainerStyle is a large style, mainly because the Template for the ListViewItem is so large. Don’t let this put you off considering using this approach because it does mean that you can reuse the style across various lists that may have different content.

Summary

Wow, sorry this ended up being quite a long post but hopefully you’ll find it useful and get some insight on the various XAML based options available to you. Don’t forget that all the options presented for UWP are able to be taken cross platform using the Uno Platform.

In summary, yes, XAML is verbose but it does come with options. If you spend time maintaining your XAML you’ll find that it is easy to understand and you can leverage the designer support in Blend and Visual Studio along the way.

I don’t believe that looking at the number of lines of XAML (or C# code) is productive. What is useful is to look at the options that are available and the easy with which you can achieve the desired outcome.

DotNetMaui (Xamarin.Forms) is Not a XAML Platform

Yeh I know I’m going to get a ton of abuse about how this title is just click bait but before you start with the comments, hear me out. Firstly, the title is actually just missing a word DotNetMaui is Not JUST a XAML Platform In this post we’ll go through why DotNetMaui/Xamarin.Forms is/is not … Continue reading “DotNetMaui (Xamarin.Forms) is Not a XAML Platform”

Yeh I know I’m going to get a ton of abuse about how this title is just click bait but before you start with the comments, hear me out. Firstly, the title is actually just missing a word

DotNetMaui is Not JUST a XAML Platform

In this post we’ll go through why DotNetMaui/Xamarin.Forms is/is not a XAML platform and discuss the inclusive approach that the team has taken that allows for the inclusive of approaches such as those proposed by Vincent (C# markup) and James (Comet).

I want to start off by saying that Xamarin.Forms is a great cross-platform technology for rapidly building apps that work across iOS, Android, Windows (UWP), MacOS etc. At Built to Roam we continue to deliver apps for customers using this technology. DotNetMaui will continue this trend and will no doubt be a great platform for developers to build apps that work across various operating systems and devices. This post is in no way supposed to be a criticism, rather an objective look at what DotNetMaui/Xamarin.Forms is and is not. This is 100% from my perspective and I respect that other developers are entitled to their opinions (feel free to leave a comment!)

XAML as Declarative Markup

Historically, there have been various technologies/frameworks that have used XAML as the markup language. Typically, we think of the UI frameworks such as Windows Presentation Foundation (WPF), Silverlight, Windows Phone, Windows (UWP) but XAML has been used as the markup language for other non-UI technologies, for example Windows Workflow Foundation. XAML is fundamentally about declarative markup and in the case of UI frameworks it was for describing layout.

For those developers who have built, or are even still building, using Windows Forms, you’ll remember the pain of trying to coerce the designer to behave. The designer was really just a glorified code generator but the frustration came because if you attempted to modify the generated code, when you reopened the designer it would undo or break your changes. With XAML, this shouldn’t happen because, assuming it’s well-formed, it adheres to the schema…. in theory anyhow.

At this point it’s worth pointing out that in some regards XAML is literally just a way of declaring the creation of a hierarchy of objects. In fact, I’ve often added instances of non-UI classes in XAML just so that they’re available as resources that I can reference from XAML or code throughout my application. If you continue down this line of reasoning, it’s no surprise that you can indeed create your UI in markup (a proposition being peddled heavily by Vincent with his contribution to C# markup for Xamarin.Forms).

XAML Databinding

When developers think about XAML, they often package that together with MVVM and/or data binding. This is not unreasonable since the data binding framework was one of the main selling points that Microsoft would talk about when promoting any XAML technology.

Xamarin.Forms does have great support for data binding. Whilst it doesn’t support the x:Bind syntax introduced in Windows (UWP) which uses code generation to make data binding strongly typed, it does support compiled bindings making XAML quite an efficient technique for defining layout and data binding.

Here’s the question though – if the compiler is just going to convert XAML into compiled code, why don’t we just write it in C# and remove the need to learn the XAML abstraction? (i.e. back to Vincent’s point about C# markup!)

Model-View-Update (MVU)

Over the last couple of years there have been a number of new frameworks and technologies that have provided alternative strategies for updating the UI. Web frameworks such as React use a virtual DOM to deliver rendering efficiencies. More recently we’ve seen Flutter take the learnings from building the rendering engine behind Chrome and applying it as an application framework. This has lead to a lot of excitement about MVU (for the moment I’m going to ignore the argument that Flutter, SwiftUI and Comet/DotNetMaui are not MVU as proposed by the Elm architecture).

There have been several spikes on implementing MVU style frameworks for both Xamarin.Forms and Windows/UWP. All of them that I’ve seen focus on replacing XAML with C# code that declares the layout, with some smarts that only push differences to the UI rendering engine.

As Xamarin.Forms evolves to DotNetMaui we’re going to see some changes to the rendering framework. As the work by James Clancey on Comet is integrated there will be changes to the use of platform renderers. Microsoft is taking a gamble that as they make the necessary changes to Xamarin.Forms/Xamarin.iOS/Xamarin.Android/UWP to align with the .NET roadmap, they’ll take this opportunity to address some of the limitations/frustrations felt by developers with the current rendering engine.

Rendering Engine

There is no proposal to fundamentally change the way that Xamarin.Forms/DotNetMaui uses the native platform controls. The premise is that apps should use the controls that have been provided by the platform. This should encourage developers to build apps that belong on each platform. This thinking is dated, and most customers don’t care about platforms details, they just want their apps to look consistent on every platform.

This lead the team to add the Visual attribute and subsequently the Material Visual. If you’re looking to build a great looking app out of the gate, using the Material Visual is the way to go. However, you do have to ask yourself why did they need to introduce the concept of a Visual when we have styles? and can’t we just change the template of controls to change how they look?

Lookless Controls make a XAML Platform

I’ve conceded early in this post that a XAML platform is really any technology that uses XAML as a markup; thus DotNetMaui/Xamarin.Forms is indeed a XAML platform. However, when we consider what we mean by a XAML platform we typically associate it with a number of capabilities (in no particular order, and I’m sure there are other things that I’ve left off the list):

  • Styles and Resources
  • Data binding
  • Data Templates
  • Visual States
  • Control Templates

Further more I would go so far to say that one of the core principles of a XAML platform is that of Lookless Controls (this term seems to have been lost to history somewhat but was heavily used in the context of WPF and the way controls were templated). This is the ability to completely re-template a control without losing the basic functionality. This is something that has never been adopted by Xamarin.Forms and isn’t likely to emerge in DotNetMaui. In order for a platform to support the concept of Lookless Controls, each control needs to have a ControlTemplate that can be used to define both the static look and feel of the control but also the various Visual States (and transitions) of the control.

Developers considering different cross platform technologies might ask what the difference is between the DotNetMaui/Xamarin.Forms approach and that taken by the Uno Platform. Without listing all the differences, one of the core differences is the support for Lookless Controls and control templating. As ControlTemplates and Visual States are fundamental to the delivery of Lookless Controls on the Windows (UWP/WinUI) platform, they are a core part of the way that the Uno Platform renders apps on each platform.

Not JUST a XAML Platform

The last section seems like I’m being overly negative on the DotNetMaui/Xamarin.Forms platform and I must admit for the longest time I was frustrated that the team hadn’t prioritised making it easier for developers to re-template controls. Whilst I still prefer a platform where I’m in control and can change the template of controls as needed, I recognise that in the majority of cases, this isn’t required in order to deliver great looking apps.

If we look at what’s in scope for DotNetMaui we see that there inclusive approach will provide developers with many options to develop and style their application. This will make DotNetMaui a great starting point for developers wanting to build apps using the Microsoft tool chain, and of course provide a great feeder into building apps that connect and work well with Azure.

Picking Your Platform

At this point you might be asking yourself how do you decide which platform to pick for building your next cross platform application. This is a question that I ask daily and the answer is that it really depends on the situation, the customer, the development team etc. So for a minute, lets limit the scope to the Microsoft ecosystem and consider the following three options:

  • Power Apps – I haven’t talked about these in this post but if you’re looking for a minimal code platform that connects to Microsoft 365 and the Power platform, then this is your best option. It’s not really a true cross platform app platform but worth considering as it’s massively powerful for working with enterprise data and workflows
  • DotNetMaui/Xamarin.Forms – If you’re looking to rapidly deliver great looking apps where the designs are derived from the Material design language without wanting to curate animations, effects and styles, then DotNetMaui/Xamarin.Forms is the way to go. Pick the approach (eg MVVM or MVU) and language (eg C# or XAML) that suits your team.
  • Uno Platform/WinUI – If you’re looking for granular control over every aspect of your application then you can’t go past the Uno Platform. This platform is evolving at an amazing pace providing support for Web Assembly, Skia backend and much more. Each control has a ControlTemplate that you can override and customise precisely the way you want it.

Consuming REST API with Swagger / OpenAPI in Xamarin and Uno Applications

I still recall the simplicity of standing up a SOAP service and adding a service reference via Visual Studio by simply entering the url to the WSDL – this scenario just worked…. until we moved on. There was a rapid progression away from the overly prescriptive XML based world of SOAP to REST based APIs. … Continue reading “Consuming REST API with Swagger / OpenAPI in Xamarin and Uno Applications”

I still recall the simplicity of standing up a SOAP service and adding a service reference via Visual Studio by simply entering the url to the WSDL – this scenario just worked…. until we moved on. There was a rapid progression away from the overly prescriptive XML based world of SOAP to REST based APIs. This was all well and good but there was no longer a standard approach to documenting these APIS. Enter Swagger, and subsequently OpenAPI, as a way to document REST based APIs. Rather than spend time in this post detailing how you can add some Swagger to your web api (look here if you’re interested), I’m going to focus on consuming a REST based API by first importing its Swagger and using that to generate the code for accessing the API.

One of the most important points to note about this process, is that we’ll be working with a .NET Standard 2.0 class library. This means that the same library can be used across any application you might want to build, whether it be a console application, or a Xamarin.Forms app, or a Uno app (go to https://platform.uno/ to learn more about the Uno Platform).

In fact, to illustrate this point, I have a solution which has both a Uno and a Xamarin.Forms application in it. The solution also has a Data project (just a regular .NET Standard 2.0 library), which is where we’ll be importing the Swagger document. The Data project is referenced by each of the head projects for both Xamarin.Forms and Uno.

The REST service that we’re going to call is one that I setup as part of generating the OCS files for Build 2020 (more details here). The swagger documentation can be found at https://build2020.builttoroam.com/swagger/index.html, which is just a pretty version of the actual Swagger/OpenAPI document that’s located at https://build2020.builttoroam.com/swagger/v1/swagger.json

The process for referencing a REST API that has a Swagger or OpenAPI is now one of the global tools that ships with the dotnet cli. High level documentation for the OpenAPI tool can be found on the Microsoft docs website. We’ll step through the process here as it’s relatively simple.

To start with, we need to make sure that the openapi tool has been installed. This can be done by running the following at a command prompt:

dotnet tool install -g Microsoft.dotnet-openapi

Next, navigate to the folder of the project you want to reference the swagger document and call the openapi command, along with the “add url” parameter (and of course the url of the swagger document).

dotnet openapi add url https://build2020.builttoroam.com/swagger/v1/swagger.json

Returning to Visual Studio we can see that a file, swagger.json, has been added to the Data project.

We’ll add a small snippet of code to call one of the REST APIs.

var client = new swaggerClient("https://build2020.builttoroam.com", new System.Net.Http.HttpClient());
var sessions = await client.SessionsAsync();
System.Diagnostics.Debug.WriteLine(sessions.Count);

This is all well and good but what happens if you want to customize the generated code….oh wait, what….generated code… I forgot to mention, as part of building the Data library, the swagger.json document that was added to your project is converted into a set of classes that make invoking the REST APIs super easy (as you saw above in the code example).

If you press F12 when the cursor is located in swaggerClient class name (or just right-click on the swaggerClient class and select Go To Definition) you’ll be taken to the generated swaggerClient.cs file (in the obj folder). Don’t make changes directly to this file as they will get overwritten the next time you build the Data project.

As I was about to say, there are a couple of ways you can customize the generated code. The first way is to adjust the code generation itself. You can do this by supplying options that configure the way the code generation is done. When the “dotnet openapi” command was invoked earlier, in addition to adding the swagger.json file to the project, an entry was added to the csproj file.

<OpenApiReference Include="swagger.json" SourceUrl="https://build2020.builttoroam.com/swagger/v1/swagger.json" />

The OpenApiReference element in the csproj invokes the code generation during the build process by calling out to the popular NSwag command line tool. As such the options that can be used are the same as for the Nswag tool (A full list of options can be found in the NSwag source code here). For example, let’s ensure an interface is generated for the swaggerClient class, we can set the GenerateClientInterfaces property to true.

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFramework>netstandard2.0</TargetFramework>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
		<PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
	</ItemGroup>
	<ItemGroup>
		<OpenApiReference Include="swagger.json" SourceUrl="https://build2020.builttoroam.com/swagger/v1/swagger.json">
			<Options>/GenerateClientInterfaces:true</Options>
		</OpenApiReference>
	</ItemGroup>
</Project>

The other way we can extend the swaggerClient class is by adding the implementation of one or more of the partial methods that has been created. Again, if you go to definition on the swaggerClient class, you can see these partial method declarations.

To implement these partial methods, simply add a partial class called swaggerClient to the project, and add the method you want to implement. For example, adding the PrepareRequest method, you can log out the actual url of each API call.

And there you have it, a super simple way to reference a REST API from a .NET Standard library, which can then be used by your Xamarin.Forms, Uno or even a console app.

Big shout out to Rico Suter for the amazing work he’s done with the NSwag library