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.

Building Messages in Blend for Visual Studio

The other day Martin Zikmond tweeted about a messaging sample app he’d built using the Uno Platform, allowing the same app to run on iOS, Android, Windows, MacOS and Web. Whilst the concept was simple enough, the point was that there was almost no platform specific code and yet the app works and looks virtually … Continue reading “Building Messages in Blend for Visual Studio”

The other day Martin Zikmond tweeted about a messaging sample app he’d built using the Uno Platform, allowing the same app to run on iOS, Android, Windows, MacOS and Web. Whilst the concept was simple enough, the point was that there was almost no platform specific code and yet the app works and looks virtually the same on every platform.

Following my previous post where I did a walk through of some of the Blend for Visual Studio features I use, I thought I’d take the challenge to see how much of Martin’s app I could build using the designer in Blend. The good news is that you can get a long way; The bad news is that there are some features, such as ThemeResources, that seem to cause some issues with the designer – we’ll see this towards the end of the post.

New Project

Ok, so this is pretty self explanatory but Blend has a similar New Project experience to what you see in Visual Studio. Start by searching for the type of project you want to build. So that the designer works, we’re going to select a Blank App (Universal Windows). Once we’ve completed the design work we can copy the files across into our Uno project

Note: At the moment there’s no support for Shared Projects, which the default Uno solution is setup to use. If you follow my post on using multi-targeting with Uno you can use the designer if you load the Windows solution filter.

Give your project a name and location you want it saved.

At this point I typically make sure the application runs and that I’ve upgraded various NuGet packages. Next I’m going to copy in a bunch of the code files from Martin’s project that is available on GitHub. This includes the ViewModels, the SampleData and a couple of Assets.

In the code behind for the MainPage (MainPage.xaml.cs) I’ve added a ViewModel property that returns an instance of the MainViewModel. This will be used to provide both runtime and design time data.

Now let’s get to the designer. From the Assets tool window, search for “split” and drag the SplitView onto the design surface. The SplitView has a Pane, the collapsable panel that shows/hides when you tab the burger menu, and a main Content area. In Martin’s app the list of conversations is in the Pane, whilst the Messages for the selected conversation appear in the Content area.

I wanted to reuse the same color resources that Martin has, so I copied the contents of the App.xaml across to my project – It would be awesome if Microsoft could add back support for managing project resources, which was previously a feature of Blend but got lost in the update to the designer a couple of years ago.

Back to the designer and focus on the SplitView. From the Properties tool window locate the PaneBackground property. Select the Color Resources tab (yeh, Editor and Color Resources are a tab, not that it’s very obvious) and select the NavPaneBackgroundColor (this comes from the resources we added to App.xaml). Whilst we’re here, set the BackgroundSource property to HostBackdrop and TintOpacity to 50%. Check out the docs on Acrylic material for more information on using Windows specific brushes. This is one point where Martin has provided a different background for Windows versus the other platforms – check out the source code to see how he’s done this using platform namespaces.

When you launch the app on Windows you can see the effect of setting the Acrylic background on the pane of the splitview (left side of this image).

Next we’re going to set the Padding on the Grid located inside the Pane, which will inset the list of conversations away from the edge. Rather than just setting the Padding, which can be done using the Properties tool window, I’m going to convert this into a reusable resource. Click the square to the right of the property to display the context menu, then select Convert to New Resource.

Give the resource a name and specify where you want the resource to be saved.

In this case I’m going to go ahead and create a new Resource dictionary by clicking the New… button. Since we’ll use this resource dictionary for styles, templates and other resources, I’ve named it accordingly. When you click the Add button, not only does this resource dictionary get added to your project, it is also wired up as a merge dictionary in App.xaml.

Next we’re going to add a ListView to the Pane to display the list of conversations. Again from the Assets window, drag the ListView across onto the Pane in the designer.

Unfortunately there’s no designer support for working with x:Bind. However, you will get intellisense in the XAML editor to let you know what properties are available for binding to.

Despite setting the ItemsSource property, the ListView still remains empty on the designer – as I mentioned, no designer support for x:Bind. However, with the recently introduced design time data everywhere support that’s been added, you can take advantage of regular data binding at design time.

Firstly, we’ll add an instance of the MainViewModel as design time resource. Note that this is the same as adding normal resources, just with the “d:” prefix. However, be aware that setting attributes at design time will override any runtime values whilst in the designer. This normally isn’t an issue but when specifying resources, it will only pick up the design time resources, rather than attempting to combine the resource dictionaries.

<Grid>
    <d:Grid.Resources>
        <MainViewModel x:Key="DesignViewModel" />
    </d:Grid.Resources>

Note that the XAML editor is able to assist with applying namespaces etc. After adding the MainViewModel resource I get a prompt helping me to setup and use the namespace.

Next, set the ItemsSource property on the ListView, again using the design time prefix (i.e. d:ItemsSource=”{Binding Conversations}” ). Now we should start to see items appear in the ListView.

Let’s go ahead and create a data template for determining how each item will appear. Right-click on the ListView and select Edit Additional Templates, Edit Generated Items (ItemTemplate), followed by Create Empty. Give the DataTemplate a name and specify where you want to save it.

Rather than going through each element in the item template, I’m going to add the DataTemplate from Martin’s code. However, even after adding this data template, there’s no items appearing in the ListView. Again, this is because there’s no designer support for x:Bind. Luckily we can do the same trick, this time for each property we want to data bind. in this example we’re using the design time data binding for the Text property on the TextBlock

Ok, so now we’re starting to look good.

However, notice how the time is appearing at different positions for each item in the list. This is because the list item isn’t spanning the full width of the ListView. I’ve never understood why this is the default behaviour but it dates back as long as I can remember. Luckily there’s an easy fix, which is to set the HorizontalContentAlignment on the ListView to Stretch. However, this needs to be done on the ItemContainerStyle for the ListView. Right-click on the ListView, Edit Additional Templates and then this time select Edit Generated Item Container (ItemContainerStyle), followed by Create Empty.

Give the template a name and location for saving.

From the Properties tool window, search for “horizon” and change the HorizontalContentAlignment to Stretch (far right icon).

Now, the list of conversations is looking much better.

In the main Content area, lets start by creating three rows to host the header, messages and input TextBox. Using the mouse, you can draw the grid rows, and then use the designer to define the height for the rows. The first and third row should both be set to Auto, with the second row set to *.

Let’s add the input TextBox at the bottom of the screen. Click on the chevrons on the left edge of the design to expand out the Assets flyout. Search for textbox and double-click on the TextBox to add it to the designer.

Next, right-click on the newly created TextBox and select Layout, Reset All.

Depending on where the TextBox was added, you’ll probably have to set the Grid.Row to 2 in order for it to appear in the third row. Whilst in the Properties tool window, let’s set the CornerRadius to 12 and the Margin to 20 – again, I would typically extract these to resources so they can be reused across the app (similar to CSS classes if you’re more familiar with styling for the web).

At this point I’m going to grab the rest of the XAML for the Content area from Martin’s source code, rather than walking you through adding each of the controls. There was one exception to this – in Martin’s code he has used an ItemTemplateSelector to switch between two different item templates based on whether the message is from the current user or not. I’m going to take a different approach, so I’ve replaced the ItemTemplateSelector with just a single ItemTemplate. The resulting messages list looks like the following, where it’s no longer possible to distinguish who sent which message.

To fix this issue we’re going to use converters that take a bool value and determine what value to return. The first converter returns a HorizontalAlignment, either Left or Right, depending on the input value. I’ve set it to return Stretch if the input value isn’t a bool, effectively making it the default value.

Next we’re going to make use of this converter in the item template. Here we’ve selected the Border element and are about to click the Create Data Binding menu item.

In the Create Data Binding window, we select the IsFrom property, and then at the bottom of the window, select the BoolHorizontalAlignmentConverter.

If you run the app at this point you’ll see that the messages are aligned left and right according to who sent the message but there’s no difference in style. We can do the same process but this time for setting the Style of different elements in the template; this time using a BoolStyleConverter.

The difference with the BoolStyleConverter is that it needs a TrueStyle and FalseStyle to be defined. For example for the Border we can create two styles and then an instance of the BoolStyleConverter.

And the same for the TextBlock style

And then we apply these to the item template.

And voila, we have a similar layout to what Martin had

Ok, so just in conclusion there’s a couple of points:

  • Firstly, in the style for the Border and TextBlock it’s referencing a ThemeResource to set color. This is important so that you get support for light/dark mode. However, it also seems to break the designer support, resulting in no messages appearing on the screen. I think the resolution might be that the Styles themselves have to be defined as ThemeResources.
  • Secondly, you might be asking why I chose to use converters instead of an ItemTemplateSelector. The answer is that there isn’t any right or wrong answer, I just wanted to demonstrate a different way to appear the problem. In some cases the ItemTemplateSelector is preferred, particularly if there are massive differences between the templates. In this case, I would probably go with converters because you have a single ItemTemplate, so any changes you make will affect both From and To messages, without you having to remember to update both templates.

Hopefully you’ve seen how you can take advantage of Blend for Visual Studio to start building cross-platform experiences; start with Windows (UWP) and apply it to iOS, Android, MacOS and Web using the Uno Platform

Windows (UWP) Designer in Visual Studio and Blend for Visual Studio

If you’ve been looking at the release notes for the Visual Studio previews, you’ll have noted that there’s some work being done on the XAML designer that now supports both WPF and UWP. I figured I would take this opportunity to go through and document some of the features that I use and some of … Continue reading “Windows (UWP) Designer in Visual Studio and Blend for Visual Studio”

If you’ve been looking at the release notes for the Visual Studio previews, you’ll have noted that there’s some work being done on the XAML designer that now supports both WPF and UWP. I figured I would take this opportunity to go through and document some of the features that I use and some of the new features that have appeared in the previews. Note that this isn’t an exhaustive list by any means – would love feedback on what other features you use and what you think is missing in the designer.

One of the reasons that the designer experience in Visual Studio and Blend is so relevant is that you can take the design you’ve done for your Windows app and port it across to iOS, Android, MacOS and even the web (using Web Assembly). All these platforms are available via the Uno platform – If you’re new to the Uno Platform, head over to https://platform.uno/ and get started with building cross-platform mobile, desktop and Web applications.

New Project

Ok, so let’s start by creating ourselves a new Windows (UWP) project based on the Blank App (Universal Windows) template.

As usual, give the project a name to get started – you’ll be prompted to pick which target and minimum versions of Windows you want but for the purpose of this post I just went with the defaults.

Visual Studio Designer

Once created, depending on your setup of Visual Studio, you’ll probably have the MainPage.xaml already open. If not, find MainPage.xaml in the Solution Explorer tool window and double-click it to open it. Here you can see that I have the Toolbox on the left of the design surface and the Properties window, above the Solution Explorer, on the right side. I find this layout works well for working with the designer but you can easily customise the layout of the tool windows to suit how you work.

Design in Blend

We’re actually going to switch across to Blend for Visual Studio for the rest of this post. I prefer to do any designer work in Blend because I prefer to have the tool windows in a different position when doing design work, than when writing code. Switching between Visual Studio and Blend also gives me a mental switch to go between designer mode (well at least “layout-oriented work” mode, since I’m clearly not a designer) and developer mode.

It’s worth noting that the designer experience in Visual Studio and Blend is very similar – Microsoft made the decision years ago to build a consistent experience with the majority of functionality now available in both tools. Blend still retains a number of designer oriented features, such as creating visual states and animations with storyboards, that haven’t been exposed in the Visual Studio designer.

You can easily switch to Blend by right-clicking on the project or a XAML file in Solution Explorer and selecting Design in Blend.

As you can see the tool windows are labeled slightly differently in Blend and have a different default position. Again, feel free to reposition them to suit how you work.

Zoom

If you look at the main design surface, you’ll note that the initial position of the MainPage is very small. Working at this zoom level will be quite hard as each of the elements will be small and hard to manipulate.

In the bottom left corner of the design surface there’s a series of icon buttons and a dropdown. Expanding the dropdown allows you to select from a number of predefined zoom levels.

Alternatively you can select Fit all, or Fit selection, in order to bring the whole page into view.

The other way that you can control the positioning of the design area is using the mouse:

  • Scroll up/down – Two-finger drag on touch pad, or scroll wheel on mouse
  • Scroll left/right – Hold Shift + Two-finger drag on touch pad, or scroll wheel on mouse
  • Zoom in/out – Hold Ctrl + Two-finger drag on touch pad, or scroll wheel on mouse

Adding Controls with Assets Tool Window

Let’s start to add some controls to the page. We’ll use the Assets tool window to locate the TextBlock control using the search function.

If I just click and drag the TextBlock onto the design surface, Blend will add the control where I drop it.

Objects and Timeline Tool Window

If we take a look at the XAML you’ll note that a very arbitrary margin has been set on the TextBlock

Layout – Reset All

Right-click on the TextBlock in the Objects and Timelines tool window and select Reset All from the Layout menu.

Now the TextBlock has been reposition to take up the whole page.

However because text flows from the top-left, the word TextBlock is in the top left corner of the page.

Edit Style – Apply Resource

Next, let’s increase the size of the text. Instead of manually setting FontSize, we’re going to make use of one of the existing TextBlock styles. Right-click on the TextBlock again in the Objects and Timeline window and select Edit Style, Apply Resource and then we’ll select HeaderTextBockStyle. You can read more about predefined styles and the use of typography here.

This gives our TextBlock a nice size, without hard coding font sizes and styles randomly throughout the application.

Design Surface

Up to now I haven’t given you much context for this app – we’re going to build a simple interface that shows a list of contacts. The TextBlock we’ve added so far will act as a header/title for the page, and then beneath it we’ll need a ListView showing the list of our contacts.

Drawing Grid Rows

Before we can add the ListView, let’s create two rows in the Grid that the TextBlock is sitting in. Select the Grid in the Objects and Timelines window and then on the design surface, if you move the mouse cursor near the edge of the page, you’ll see the cursor change to one that’s got a small plus sign on it. Clicking at this position will add a row to the Grid.

Once added, you can then adjust the sizing of the row. In this scenario we’re going to change the row from 33* to being Auto, which will mean the row will be sized based on the height of the TextBlock.

Reset Grid.RowSpan in Properties Tool Window

One thing you may notice is that after adjusting the row size, the row seems to disappear. This is because in creating the row, Blend decided that the TextBlock was going to span both rows. To fix this, we can change the RowSpan on the TextBlock from 2 to 1 using the Properties tool window.

In this case, we’re going to select the Reset option, rather than setting it explicitly to 1. This will mean one fewer attributes in XAML that needs to be parsed.

Assets Button

Next up, we’re going to add the ListView to the second row. This time, instead of going all the way to the Assets tool window, we’re going to use the dropdown from the chevrons on the top left of the designer. This allows us to easily search and add controls, without having to open the tool windows – when doing a lot of design work I will often hide the tool windows (or even detach them and put them on a different monitor), so not having to open the tool windows to find a control is quite handy.

Runtime Toolbar

After dragging the ListView onto the page and making sure it’s sitting in the second row, I’m going to run the application to see what it looks like. Note that despite me not doing anything, the color of the title bar has aligned with my Windows theme (and yes, it’s pink because I’m using the setting where the theme is derived off the background I have set, which in turn is changed daily using the Dynamic Theme app).

Hot Reload

You’ll also note at the top of the window there is a toolbar that’s been added whilst I’m debugging the application. This toolbar has been available for a while but the Hot Reload indicator is a new addition. Hot Reload allows you to make changes to your XAML and for it to immediately take effect on the page when you save the XAML file (Hot Reload can be configured via Options off the Tools menu).

Display Layout Adorners

I’m going to toggle the Display Layout Adorners button (immediately adjacent to the left of the Hot Reload green tick).

Select Element

Next I’m going to click on the Select Element button (second in from the left) and click on the open space immediately below the TextBlock. As you can see, this highlights the whole area in a light blue (the layout adorner) which is the ListView.

Go to Live Visual Tree

Next I can click on the Go to Live Visual Tree button (first from the left) which will switch back to Visual Studio with the focus being set on the appropriate node in the Live Visual Tree.

XAML Editor

You’ll also note that the corresponding code in the XAML editor has been selected. As I start to type in the XAML editor I get intellisense showing me what options I have.

I’ve gone ahead and set a simple data template for the ListView which will determine what’s displayed for each item in the list. In this case it’s just going to print the word “Hi” for each item.

XAML Highlighting

Note that as I move the cursor around the XAML editor, the matching pairs of XML tags are highlighted, making it easy to see start and end of the blocks of XAML.

DataContext for Data Binding

Currently, despite setting a data template, the ListView isn’t going to show any items because I haven’t connected it to any data. To allow me to continue to layout elements on the page I’ve created some mock, or fake, data.

public class FakeData
{
    public Contact[] Contacts { get; } = new[]
    {
        new Contact(){Name="Bob Jones",PhoneNumber="+1 442 002 3234", Photo="ms-appx:///Assets/Photos/p0.jpg"},
        new Contact(){Name="Jessica Phelps",PhoneNumber="+1 394 234 1235", Photo="ms-appx:///Assets/Photos/p1.jpg"},
        new Contact(){Name="Andrew Jenkins",PhoneNumber="+1 232 282 29321", Photo="ms-appx:///Assets/Photos/p2.jpg"},
        new Contact(){Name="Francis Davis",PhoneNumber="+1 92329 2923 923", Photo="ms-appx:///Assets/Photos/p3.jpg"},
        new Contact(){Name="Xavier Smith",PhoneNumber="+1 93483 3923423", Photo="ms-appx:///Assets/Photos/p4.jpg"},
        new Contact(){Name="Kevin Chow",PhoneNumber="+1 343 994 39342", Photo="ms-appx:///Assets/Photos/p5.jpg"},
        new Contact(){Name="Phil Stevenson",PhoneNumber="+1 885 367 44432", Photo="ms-appx:///Assets/Photos/p6.jpg"},
        new Contact(){Name="Heath Sales",PhoneNumber="+1 903 912 9392", Photo="ms-appx:///Assets/Photos/p7.jpg"},
        new Contact(){Name="Sarah Wright",PhoneNumber="+1 347 399 499234", Photo="ms-appx:///Assets/Photos/p8.jpg"},
        new Contact(){Name="Geoff Sans",PhoneNumber="+1 834 1232 01923", Photo="ms-appx:///Assets/Photos/p9.jpg"}
    };
}

I can go ahead and create an instance of the FakeData class as a Resource on the Page. This instance is then set as the DataContext for the root Grid element on the page (and subsequently the DataContext for all elements on the page).

The ItemsSource on the ListView is then data bound to the Contacts property on the FakeData instance. The running application updates immediately to show a list of “Hi” down the screen.

Hide the Runtime Toolbar

As we start to design the page, the toolbar at the top can often get in the way. If you click on the chevron on the right side of the toolbar, the first click will reduce the size of the Hot Reload indicator (removing the words Hot Reload). The second click will minimise the toolbar completely.

Let’s amend the data template for the list, this time data binding to the Name property.

The ListView is immediately looking better, showing the names of the people in the Contacts list.

Edit ListView Item Template

If your working in the designer there are a number of ways you can make changes to the data template for the list. In this example we’re using the dropdown at the top left corner of the design area. Clicking the down button, followed by Edit Additional Template, then Edit Current, then Edit Generated Items (ItemTemplate).

Objects and Timeline – Group Into

You’ll notice that the Objects and Timeline window updates to show the tree from the perspective of the data template. Right-clicking on the TextBlock I can select Group Into, followed by StackPanel, to wrap the TextBlock in a StackPanel.

TextBlock Button

Next, I can add a TextBlock to the newly created StackPanel but simply double-clicking the TextBlock button on the left of the design area.

XAML Suggested Actions

One of the newest additions to the designer is the XAML Suggested Actions. Anyone who’s worked in Word or Excel is familiar with the suggested actions toolbar that often appears to try to make more of the tools are accessible when you need them. in this case we can click on the lightbulb button to display an in-situ property editor.

In this case we’re going to click through to Create Data Binding.

Create Data Binding

From the Create Data Binding window we can traverse down to the PhoneNumber property on the Contact object.

Note that there’s a minor bug at this point because the data binding that’s created isn’t 100% accurate. Instead of data binding to the PhoneNumber property (eg {Binding PhontNumber}) the generated binding is with Contacts.PhoneNumber. Simply removing the “Contacts.” prefix is enough to get it to work.

Edit ColumnDefinitions

I’m going to follow a similar approach to before – select the StackPanel and group into a Grid. From the Properties tool window find the ColumnDefinitions.

Click the … to open the Collection Editor for the ColumnDefinitions. Here we’re going to limit the first column to 50 pixels and then leave the second column as *.

I’ve update the data template with the profile picture as an image. Simply add an Image control and follow the same process as before to setup data binding to the Photo property on the Contact.

WinUI – ProfilePicture Control

To improve the layout of the contacts I’m going to use the ProfilePicture control, which will ensure all the images are the same size.. Instead of using the built in control, I’m going to grab the ProfilePicture control from the WinUI toolkit. Using the nuget package manager, select the Microsoft.UI.Xaml package and install it.

Once you’ve added the nuget package, make sure you don’t ignore the readme file that’s displayed to remind you to include the appropriate runtime values.

We’re almost there, we just need to add the PersonProfile, which we can do by discovering it in the Assets window (even though its from a third party)

And so now we have all our contacts appearing.

Final Design

After applying some minor tweaks to the layout of the data set, we can have a nicer looking sample app.

Before I copied the final code to this post, I just wanted to format the XAML. From the Options window, you can configure how you want your XAML files to appear.

Then, once you have the formatting options the way you want them, you can invoke Format Document from the various places around the design surface.

Final XAML

And here’s the final source code

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:WindowsDesigner"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:Custom="using:Microsoft.UI.Xaml.Controls"
      x:Class="WindowsDesigner.MainPage"
      mc:Ignorable="d">
    <Page.Resources>
        <local:FakeData x:Key="DesignTimeData" />
    </Page.Resources>

    <Page.Background>
        <ThemeResource ResourceKey="ApplicationPageBackgroundThemeBrush" />
    </Page.Background>

    <Grid DataContext="{StaticResource DesignTimeData}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock Margin="16"
                   Text="Contacts"
                   TextWrapping="Wrap"
                   Style="{StaticResource HeaderTextBlockStyle}" />
        <ListView Grid.Row="1"
                  ItemsSource="{Binding Contacts}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid Margin="8">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="50" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <StackPanel Orientation="Vertical"
                                    Grid.Column="1"
                                    Margin="8"
                                    HorizontalAlignment="Center">
                            <TextBlock Text="{Binding Name}"
                                       Style="{StaticResource BaseTextBlockStyle}" />
                            <TextBlock Text="{Binding PhoneNumber}"
                                       Style="{StaticResource BodyTextBlockStyle}" />
                        </StackPanel>
                        <Custom:PersonPicture Width="50"
                                              Height="50"
                                              ProfilePicture="{Binding Photo}" />
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>

And that’s it – hopefully you’ve learnt a bit more about how to use the XAML editor and the new designer features.

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

iOS Hot Restart, Multi-Targeting and Solution Template for Cross-Platform Applications with Uno

In my last couple of posts I covered setting up multi-targeting and improving the developer experience with multi-targeting. This was in the context of an application that supported iOS (Xamarin iOS), Android (Xamarin Android) and Windows (UWP). In this post we’re going to look at a template for Uno cross-platform applications. If you’re new to … Continue reading “iOS Hot Restart, Multi-Targeting and Solution Template for Cross-Platform Applications with Uno”

In my last couple of posts I covered setting up multi-targeting and improving the developer experience with multi-targeting. This was in the context of an application that supported iOS (Xamarin iOS), Android (Xamarin Android) and Windows (UWP). In this post we’re going to look at a template for Uno cross-platform applications.

If you’re new to the Uno Platform, head over to https://platform.uno/ and get started with building cross-platform mobile, desktop and Web applications.

The main points we’ll review are:

  • Creating a Uno application that targets iOS, Android, MacOS, Windows and Wasm (browsers/web).
  • Add class library, Core, for housing business logic (eg ViewModels)
  • Add class library, UI, for housing pages, controls etc
  • Add Hot Restart to the iOS head project
  • Add target framework switching and solution filters.
  • Refactoring project files to extract Uno references to make it easier to update references

If you want to jump straight to the final project/solution structure, then you can take a look at the final source code on the GitHub repository

Creating Your First Uno Application

For the purposes of this post we’re going to work with a new Uno application. However, there’s no reason why you can’t retrofit any, or all, of these steps to an existing project.

Start by searching for the Uno application template (If you don’t have these templates, then install them from the marketplace).

Give the application a name – We’ll use MultiTargetingWithUno.

Creating Uno Class Libaries

In this example, we’re going to create separate class libraries to house our pages and our ViewModels. But before we get on with creating them, we’re going to do a quick bit of tidying up.

Create PlatformHeads Solution Folder, and move all existing projects into PlatformHeads folder. This will group the head projects (i.e. the application projects that you can deploy and run) so that they don’t have other projects intermingled with them.

The next thing to do is to create two libraries: Core and UI. Search for Uno again but this time pick the Cross Platform Library option.

Next, give your library a name, and then repeat the process for both MultiTargetingWithUno.Core and MultiTargetingWithUno.UI libraries.

Delete the default Class1.cs file from both Core and UI projects, and make sure there is a reference from the UI project to Core.

Add reference to both UI and Core projects to the Shared project (so it gets added to each head project).

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    . . .
    <ItemGroup>
    <ProjectReference Include="..\..\MultiTargetingWithUno.Core\MultiTargetingWithUno.Core.csproj" />
    <ProjectReference Include="..\..\MultiTargetingWithUno.Core\MultiTargetingWithUno.UI.csproj" />
  </ItemGroup>
</Project>

MSBuild.Sdk.Extras

This step is primarily around refactoring the Core and UI projects to make them easier to maintain by relocating various properties so that both projects share the same declarations.

In both Core and UI project files, the sdk references is MSBuild.Sdk.Extras/2.0.54. Change this by removing the specific version number, i.e. 2.0.54. Instead of specifying the version of the package inline, we’re going to specify it in a global.json file located in the solution folder.

Inside the globa.json we just need to include the sdk and its corresponding version number.

{
    "msbuild-sdks": {
        "MSBuild.Sdk.Extras": "2.0.54"
    }
}

Your solution structure should look similar to the following.

Next we’re going to add a new solution item, libraries.targets. This is going to contain most of the properties that are in common across the Core and UI projects. This way if we need to adjust properties, we can do it in one place and have it applied to both libraries.

<Project>
	<PropertyGroup>
		<TargetFrameworks>netstandard2.0;xamarinios10;xamarinmac20;MonoAndroid90;monoandroid10.0;uap10.0.16299</TargetFrameworks>
		<!-- Ensures the .xr.xml files are generated in a proper layout folder -->
		<GenerateLibraryLayout>true</GenerateLibraryLayout>
	</PropertyGroup>
	<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
		<DefineConstants>$(DefineConstants);__WASM__</DefineConstants>
	</PropertyGroup>
	<ItemGroup Condition="'$(TargetFramework)'=='xamarinios10' or '$(TargetFramework)'=='MonoAndroid90' or '$(TargetFramework)'=='monoandroid10.0' or '$(TargetFramework)'=='netstandard2.0'">
		<PackageReference Include="Uno.UI" Version="2.4.0" />
	</ItemGroup>
	<ItemGroup>
		<Page Include="**\*.xaml" Exclude="bin\**\*.xaml;obj\**\*.xaml" />
		<Compile Update="**\*.xaml.cs">
			<DependentUpon>%(Filename)</DependentUpon>
		</Compile>
	</ItemGroup>
	<ItemGroup>
		<UpToDateCheckInput Include="**\*.xaml" Exclude="bin\**\*.xaml;obj\**\*.xaml" />
	</ItemGroup>
</Project>

Update the Core csproj file to remove everything inside the Project element, except the Import that references the newly created libraries.targets file.

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="MSBuild.Sdk.Extras">
	<Import Project="..\libraries.targets" />
</Project>

Repeat this for the UI csproj file. However, you need to keep the project reference to the Core project.

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="MSBuild.Sdk.Extras">
	<Import Project="..\libraries.targets" />
	<ItemGroup>
		<ProjectReference Include="..\MultiTargetingWithUno.Core\MultiTargetingWithUno.Core.csproj" />
	</ItemGroup>
</Project>

Target Framework Switching and Solution Filtering

As we did in my previous post, we’re going to update the Core and UI projects to allow for easy switching between active target framewrks.

We first need to add Directory.build.props to the solution folder. This is used to setup various project properties that can be used to conditionally set what target frameworks are defined.

<Project>
	<PropertyGroup>
		<IsWasmHeadProject>$(MSBuildProjectName.Contains('.Wasm'))</IsWasmHeadProject>
		<IsAndroidHeadProject>$(MSBuildProjectName.Contains('.Droid'))</IsAndroidHeadProject>
		<IsiOSHeadProject>$(MSBuildProjectName.Contains('.iOS'))</IsiOSHeadProject>
		<IsMacOSHeadProject>$(MSBuildProjectName.Contains('.macOS'))</IsMacOSHeadProject>
		<IsWindowsHeadProject>$(MSBuildProjectName.Contains('.UWP'))</IsWindowsHeadProject>
		<IsLibraryProject>!$(IsWasmHeadProject) and !$(IsAndroidHeadProject) and !$(IsiOSHeadProject) and !$(IsMacOSHeadProject) and !$(IsWindowsHeadProject)</IsLibraryProject>
	</PropertyGroup>

	<PropertyGroup>
		<IsWindows>$(TargetFramework.StartsWith('uap'))</IsWindows>
		<IsAndroid>$(TargetFramework.StartsWith('monoandroid'))</IsAndroid>
		<IsiOS>$(TargetFramework.StartsWith('xamarinios'))</IsiOS>
		<IsMac>$(TargetFramework.StartsWith('xamarinmac'))</IsMac>
		<IsWasm>$(TargetFramework.StartsWith('netstandard'))</IsWasm>
	</PropertyGroup>

	<PropertyGroup>
		<TargetsToBuildDeveloperOverride>All</TargetsToBuildDeveloperOverride>
	</PropertyGroup>

	<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
		<TargetsToBuild>$(TargetsToBuildDeveloperOverride)</TargetsToBuild>
	</PropertyGroup>

	<PropertyGroup Condition=" '$(Configuration)' != 'Debug' ">
		<TargetsToBuild>All</TargetsToBuild>
	</PropertyGroup>
</Project>

Next, update the libraries.targets. The main changes are in bold, where the TargetsToBuild property is used to determine what target frameworks are defined.

<Project>
	<PropertyGroup Condition=" '$(TargetsToBuild)' == 'All' ">
		<TargetFrameworks>netstandard2.0;xamarinios10;xamarinmac20;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>

	<PropertyGroup Condition=" '$(TargetsToBuild)' != 'All' ">
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Android' ">monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Windows' ">uap10.0.16299</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'iOS' ">xamarinios10</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Mac' ">xamarinmac20</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Wasm' ">netstandard2.0</TargetFrameworks>
	</PropertyGroup>

	<ItemGroup>
		<Compile Remove="**\*.netstandard.cs" Condition="'$(IsWASM)' != 'true'"/>
		<Compile Remove="**\*.droid.cs" Condition="'$(IsAndroid)' != 'true'"/>
		<Compile Remove="**\*.mac.cs" Condition="'$(IsMac)' != 'true'" />
		<Compile Remove="**\*.ios.cs" Condition="'$(IsiOS)' != 'true'"/>
		<Compile Remove="**\*.windows.cs" Condition="'$(IsWindows)' != 'true'" />
	</ItemGroup>
	
	<PropertyGroup>
		<GenerateLibraryLayout>true</GenerateLibraryLayout>
	</PropertyGroup>
	<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
		<DefineConstants>$(DefineConstants);__WASM__</DefineConstants>
	</PropertyGroup>
	<ItemGroup Condition="'$(TargetFramework)'=='xamarinios10' or '$(TargetFramework)'=='MonoAndroid90' or '$(TargetFramework)'=='monoandroid10.0' or '$(TargetFramework)'=='netstandard2.0'">
		<PackageReference Include="Uno.UI" Version="2.4.0" />
	</ItemGroup>
	<ItemGroup>
		<Page Include="**\*.xaml" Exclude="bin\**\*.xaml;obj\**\*.xaml" />
		<Compile Update="**\*.xaml.cs">
			<DependentUpon>%(Filename)</DependentUpon>
		</Compile>
	</ItemGroup>
	<ItemGroup>
		<UpToDateCheckInput Include="**\*.xaml" Exclude="bin\**\*.xaml;obj\**\*.xaml" />
	</ItemGroup>
</Project>

So that we can see what target framework was built, we’ll add the AppProperties.cs and each platform file (eg AppProperties.android.cs) that we created in the previous post.

In order to switch target frameworks, add the launch batch files and solution filters – see previous post.

MainPage and MainViewModel

Ok, so now we can get on to adding some functionality. We’ll start by creating a ViewModels folder in Core project and then adding a basic class, MainViewModel.

namespace MultiTargetingWithUno.Core.ViewModels
{
    public class MainViewModel
    {
        public string WelcomeText { get; } = "Hello World!";
    }
}

The MainPage already exists, but it’s currently in the Shared project. Move the MainPage to UI project. After moving the MainPage, make sure you double check the csproj file for the UI project – Visual Studio has a nasty habit of adding unnecessary elements to the csproj. In this case, you shouldn’t have any explicit entries for MainPage.xaml or MainPage.xml.cs, since they will both be added to the project by default with the correct build action.

Update MainPage to correct the namespace from MultiTargetingWithUno.MainPage to MultiTargetingWithUno.UI.MainPage. We’ll also update the Text attribute on the TextBlock to use x:Bind to show the welcome message from WelcomeText property on the the ViewModel that’s created in the code behind.

<Page
    x:Class="MultiTargetingWithUno.UI.MainPage"
    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">
    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{x:Bind ViewModel.WelcomeText}" Margin="20" FontSize="30" />
    </StackPanel>
</Page>

And the code behind which instantiates the MainViewModel.

using MultiTargetingWithUno.Core.ViewModels;

namespace MultiTargetingWithUno.UI
{
    public sealed partial class MainPage 
    {
        public MainViewModel ViewModel { get; } = new MainViewModel();

        public MainPage()
        {
            InitializeComponent();
        }
    }
}

Update App.xaml.cs in the Shared project to reference the MainPage (you may need to rebuild the solution in order for the intellisense to kick in and help you add the necessary using statements)

Set Windows head project (ie MultiTargetingWithUno.UWP) as the startup project and attempt to build and run the application.

If you see a message in Output window such as “Project not selected to build for this solution configuration”, change the Solution Platform to either x86 or x64 (alternatively you can modify the configuration to include the UWP head project when AnyCPU is selected).

Sometimes building for the first time you may need to force a rebuild of Core, UI and then the UWP head project.

At the time of writing, if you’d followed the steps to this point you’ll actually get an error similar to “error CS0103: The name ‘InitializeComponent’ does not exist in the current context”.

This is because the Uno.UI package hasn’t been added to the Mac target framework. To fix this, change the Condition for ItemGroup in the UI csproj from “‘$(TargetFramework)’==’xamarinios10’ or ‘$(TargetFramework)’==’MonoAndroid90’ or ‘$(TargetFramework)’==’monoandroid10.0’ or ‘$(TargetFramework)’==’netstandard2.0′” to just “‘$(TargetFramework)’!=’uap10.0.16299′” eg.

<ItemGroup Condition="'$(TargetFramework)'!='uap10.0.16299'">
	<PackageReference Include="Uno.UI" Version="2.4.0" />
</ItemGroup>

And then, just when you thought we’d be done, you’ll also see another error, this time when you launch the UWP application:

System.AccessViolationException (0x80004003). Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

This is a nasty error introduced by Microsoft at some point, preventing UWP applications from launching if there’s no pages defined in the application project. The solution is to add a dummy page to the UWP head project, call it “DoNotRemoveOrUsePage.xaml”.

Now you should be able to build and run the application but as you can see, there’s no indication of what platform we’re running on.

Let’s just update the WelcomeText on the MainViewModel to return platform information

public string WelcomeText { get; } = "Hello World! " + AppProperties.AppName;

That’s looking better – at this point you should be able to run the other platforms and see the welcome text vary with each platform.

Project File Refactor

In this section we’re going to do a bit of a tidy up of the Uno package references, extracting them out of the various project files so that they’re easier to manage in a central location.

Firstly we’ll add the logging packages to Directory.build.props, since they’ll be used by the Core, UI and all the head projects.

<ItemGroup>
	<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.1" />
	<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.1" />
</ItemGroup>

Next, we’ll create a new file, uno.targets, in the solution folder. This will include the Uno.Core (which all projects will refernece), along with Imports for different platforms. This will seem quite messy, and to be honest it is, but the way this is structured is based on a limitation of the legacy project system used by UWP, iOS, Android and Mac. The legacy project system won’t process conditional ItemGroups. However, they do support conditional Import statements, such as those in the uno.targets.

<Project>
  <ItemGroup>
     <PackageReference Include="Uno.Core" Version="2.0.0" />
  </ItemGroup>

  <Import Project="uno.ui.targets"  Condition="'$(IsWindows)' != 'true' and '$(IsWindowsHeadProject)' != 'true'"/> 

  <Import Project="uno.android.targets"  Condition="'$(IsAndroidHeadProject)' == 'true'"/>
  <Import Project="uno.wasm.targets"  Condition="'$(IsWasmHeadProject)' == 'true'"/>

  <Import Project="uno.debugging.targets"  Condition="'$(Configuration)' == 'Debug'"/> 
</Project>

The uno.targets is imported into the Directory.build.props so that it gets added to each project in the solution.

<Import Project="uno.targets" />

Create the uno.ui.targets file which includes a reference to the Uno.UI package. As you can see from the uno.targets file, the Uno.UI package needs to be imported into every project other than the Windows or Windows head projects

<Project>
	<ItemGroup>
		<PackageReference Include="Uno.UI" Version="2.4.4"  />
	</ItemGroup>
</Project>

Next up is the uno.android.targets, which is used to add the Uno.UniversalimageLoader package to the Android head project.

<Project>
	<ItemGroup>
		<PackageReference Include="Uno.UniversalImageLoader" Version="1.9.32" />
	</ItemGroup>
</Project>

Then, the uno.wasm.targets includes the Bootstrap packages which are required by the Wasm head project:

<Project>
	<ItemGroup>
		<PackageReference Include="Uno.Wasm.Bootstrap" Version="1.2.0" />
		<PackageReference Include="Uno.Wasm.Bootstrap.DevServer" Version="1.2.0" />
	</ItemGroup>
</Project>

Lastly the uno.debugging.targets is only included for the Debug configuration.

<Project>
	<ItemGroup>
		<PackageReference Include="Uno.UI.RemoteControl" Version="2.4.4"  />
	</ItemGroup>
</Project>

With the uno.targets (and its nested imports) added to the Directory.build.props, we can go through an clean up any references to Uno packages throughout the various project files.

  • In libraries.targets, remove ItemGroup with Condition “‘$(TargetFramework)’!=’uap10.0.16299′” that includes a package reference for Uno.UI
  • Remove references to Uno from head projects: Uno.UI, Uno.UI.RemoteControl, Uno.UniversalImageLoader, Uno.Wasm.Bootstrap, Uno.Wasm.Bootstrap.DevServer, Microsoft.Extensions.Logging.Console and Microsoft.Extensions.Logging.Filter

The other piece of refactoring we’ll do is to add a Directory.build.targets file to the solution folder, which defines various helpful properties and constants. These are useful if you ever have to write conditional logic in your code, or tweak debug output etc.

<Project>
	<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
		<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
		<DebugType>full</DebugType>
		<DebugSymbols>true</DebugSymbols>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsWASM)">
		<DefineConstants>$(DefineConstants);NETSTANDARD;PORTABLE;__WASM__</DefineConstants>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsWindows)">
		<DefineConstants>$(DefineConstants);NETFX_CORE;XAML;WINDOWS;WINDOWS_UWP;UWP</DefineConstants>
		<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion>
		<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsiOS)">
		<DefineConstants>$(DefineConstants);MONO;UIKIT;COCOA;APPLE;IOS</DefineConstants>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsMac)">
		<DefineConstants>$(DefineConstants);MONO;COCOA;APPLE;MAC</DefineConstants>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsAndroid)">
		<DefineConstants>$(DefineConstants);MONO;ANDROID</DefineConstants>
		<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
		<AndroidResgenClass>Resource</AndroidResgenClass>
		<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
	</PropertyGroup>
</Project>

iOS Hot Restart

The last feature we’re going to add is support for iOS Hot Restart – the ability to run on an iOS device without needing a Mac! The Uno team blogged about this already but I’ll repeat it here to make things easier.

Add the following to main.cs – this will only be included in a debug build, so you don’t need to worry about it polluting your production code. Hot Restart works by pushing out a pre-built Xamarin.Forms application to the iOS device and then dynamically loading your application into it. This Uno doesn’t use Xamarin.Forms, this extra code is required to piggy-back on the process Microsoft has created for Hot Restart.

#if DEBUG
	public class HotRestartDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
	{
		public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
		{
			Windows.UI.Xaml.Application.Start(_ => new App());
			return base.FinishedLaunching(uiApplication, launchOptions);
		}
	}
#endif

Create uno.debugging.hotrestart.targets that references the Xamarin.Forms package.

<Project>
	<ItemGroup>
		<PackageReference Include="Xamarin.Forms" Version="4.6.0.800" />
	</ItemGroup>
</Project>

Update uno.debugging.targets to conditionally import the uno.debugging.hotrestart.targets for the iOS head project.

<Import Project="uno.debugging.hotrestart.targets" Condition=" '$(IsiOSHeadProject)' == 'true' " />

Launch, Build and Run

There, we’re done and ready to run out application. If you load the MultiTargetingwithUno solution file, you should see all five head projects, along with the shared project, and then two class libraries. In this mode you can pick any head project as the start up project and run that application. However, it will be slow as it will have to build every target framework for the Core and UI libraries.

iOS

If instead, you double-click the Launch.iOS.bat, it will restrict the target framework to just iOS and then launch the ios solution filter. The Solution Explorer only loads the iOS head project and the two class libaries and the build is significantly quicker as it’s only building for iOS.

Android

Next up is Android. Again by running the Launch.Android.bat you only see the Droid head project.

Windows (UWP)

Launch.Windows.bat will load the UWP project and again limit the build to only include the UWP target framework.

Web (WebAssembly / WASM)

The same process applies to WebAssembly (WASM).

MacOS

Finally for MacOS, the bat files won’t work (since they won’t run on Mac). However, you can still manually adjust the TargetsToBuildDeveloperOverride property and set it to Mac.

Solution filters aren’t supported yet by visual Studio for Mac, so you may consider creating a Mac specific solution file, rather than having to load all of the projects.

One really neat feature I noticed as i was debugging on Mac is that the application automatically supports theming – here’s screenshot running in Light and Dark mode. This also works on the other platforms!!

Now you’ve been through it, make sure you check out the raw source code at the GitHub repository

Microsoft Build 2020: Uno Platform + WinUI = Future of Cross Platform Apps

I’ve been a long time supporter of the work that the team at Uno Platform have been doing. The announcement today of v3.0 of the Uno Platform comes hot on the heals of releases 2.3 and 2.4 that introduced AndroidX and MacOS support. This isn’t just any old announcement, v3 aligns the Uno Platform with … Continue reading “Microsoft Build 2020: Uno Platform + WinUI = Future of Cross Platform Apps”

I’ve been a long time supporter of the work that the team at Uno Platform have been doing. The announcement today of v3.0 of the Uno Platform comes hot on the heals of releases 2.3 and 2.4 that introduced AndroidX and MacOS support. This isn’t just any old announcement, v3 aligns the Uno Platform with the preview of WinUI 3.

At Built to Roam we’ve been building XAML based applications across a variety of technologies: WPF, Silverlight, WinPhone/Win8, UWP. Each iteration of Windows 10, new features were added to UWP but this limited the reach of the application to devices running the latest version of Windows 10. With WinUI 3.0 Microsoft is detaching the XAML platform away from the Windows version – this means that WinUI can iterate faster and bring newer controls and features to developers. In addition, WinUI also brings with it support for Win32 based applications – more on WinUI here.

So, why is it important that Uno Platform supports WinUI 3.0? Well, if you think of WinUI extending UWP to support all Windows 10 devices, then the addition of the Uno Platform takes WinUI across iOS, Android, macOS and Web via WebAssembly #WinUIEverywhere.

For more information, check out the Announcement of Uno Platform 3.0

Pipeline Templates: Complete Azure Pipelines Example for a Uno Project for iOS, Android and Windows

My last post was a bit of a long one as it covered a bunch of steps for setting up the bits and pieces required for signing an application for different platforms. In this post I just wanted to provide a complete example that shows a single multi-stage (6 in total) Azure Pipelines pipeline for … Continue reading “Pipeline Templates: Complete Azure Pipelines Example for a Uno Project for iOS, Android and Windows”

My last post was a bit of a long one as it covered a bunch of steps for setting up the bits and pieces required for signing an application for different platforms. In this post I just wanted to provide a complete example that shows a single multi-stage (6 in total) Azure Pipelines pipeline for building a Uno application for iOS, Android and Windows (UWP) and releasing them to App Center.

Secure Files

In my last post I showed how to create and populate Secure Files in Azure Pipelines. Any certificate or provisioning profile you need to use in your pipeline should be added to the Secure Files section of the Library in Azure Pipeline. My list of Secure Files looks like this:

Here we can see that we have the signing certificates for iOS and Windows, and the keystore for Android. Then we have two iOS provisioning profiles, one for my XF application and the other for my Uno application.

Variable Groups

I’ve extracted most of the variables I use in my pipeline into one of two variable groups:

The Common Build Variables are those variables that can be reused across multiple projects.

The Inspector Uno Build Variables are those variables that are specific to this project. For example it includes the AppCenter ids for the iOS, Android and Windows applications. It also includes the iOS provisioning profile which is specifically tied to this application.

Pipeline

Here’s the entire pipeline:

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.5.0
      endpoint: github_connection
  
variables:
  - group: 'Common Build Variables'
  - group: 'Inspector Uno Build Variables'
  - name: ios_enabled
    value: 'true'
  - name: windows_enabled
    value: 'true'
  - name: android_enabled
    value: 'true'
 
stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Android'
    build_android_enabled: $(android_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Signing information
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    deploy_appcenter_enabled: $(android_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)
    # Signing information (for Android repack to APK)
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_android_appid)


- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Windows'
    build_windows_enabled: $(windows_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Signing information
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_windows_folder)
    application_package: $(windows_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    deploy_appcenter_enabled: $(windows_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_windows_folder)
    application_package: $(windows_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_windows_appid)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_iOS' 
    build_ios_enabled: $(ios_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Signing information
    ios_plist_filename: 'src/Apps/DotNet/Uno/InspectorUno/InspectorUno/InspectorUno.iOS/Info.plist'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    # Output information
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    deploy_appcenter_enabled: $(ios_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_ios_appid)


Pipeline Templates v0.5.0

Most of the v0.5.0 release has been tidying things up, reducing the number of required parameters by making the template smarter and increasing consistency across the templates

Breaking Changes:

  • build-xamarin-android.yml – changed build_platform to solution_target_platform parameter
  • build-xamarin-windows.yml – changed build_platform to solution_target_platform parameter
  • build-xamarin-windows.yml – changed windows_appxupload_name parameter to windows_upload_name to reflect support for msix

Other Changes:

  • build-xamarin-[iOS/android/windows].yml – added depends_on parameter so that the stages can be ordered
  • build-xamarin-[iOS/android/windows].yml – artifact_name, artifact_folder and application_package are no longer required and have default values
  • build-xamarin-android.yml – supports building either aab or apk based on the application_package parameter
  • build-xamarin-windows.yml – windows_upload_name parameter isn’t required as it has a default value based on the bundle name
  • deploy-appcenter.yml – added conditions to all steps so that pipeline doesn’t break if build stage doesn’t generate any output
  • All template – documentation added to parameters and steps

Pipeline Templates: Building and Deploying Uno Apps for iOS, Android and Windows

In my previous posts covering the Pipeline Templates I’ve discussed building a Xamarin.Forms apps for iOS, Android and Windows (UWP) and subsequently deploying them to AppCenter. In this post we’re going to look at doing the same with a Uno application. Given that Uno is built on top of the core Xamarin functionality, the process … Continue reading “Pipeline Templates: Building and Deploying Uno Apps for iOS, Android and Windows”

In my previous posts covering the Pipeline Templates I’ve discussed building a Xamarin.Forms apps for iOS, Android and Windows (UWP) and subsequently deploying them to AppCenter. In this post we’re going to look at doing the same with a Uno application. Given that Uno is built on top of the core Xamarin functionality, the process for both build and deploy should be fairly similar. Rather than just stepping through using the template, I’m going to cover creating a new Uno project and setting up the corresponding multi-stage pipeline to build and deploy to AppCenter.

Also, I’ve pushed v0.3.0 of the pipeline templates, which I’ll summarise at the end of this post.

Creating a Uno Application

Let’s get into it and start by creating a Uno application. Before proceeding, make sure you grab the latest Uno extension for Visual Studio that includes the project templates. From the Create a new project dialog, enter “uno” into the search box and then select the Cross-Platform App (Uno Platform) template.

Next, enter a name and location for your new application

When you hit the Create button, Visual Studio will generate a new solution with five projects, representing the head projects for iOS, Android, Windows (UWP) and Web (WASM), along with a shared project.

After creating the application, you should go through each platform and check that it builds and runs. The WASM project will take a while to build and run the first time as it needs to download some components first – don’t be alarmed it nothing appears to happen for a long time.

Build Configuration

One thing I do like to tidy up for my applications is the build configurations. This is often overlooked by developers and then wonder why they have to wait around for say the Android project to build, when they have the UWP project selected as their start up project. Here are the Release configurations I have set for the different platforms

Release Configuration with Any CPU
Release Configuration with iPhone
Release Configuration with x64 (x86 and ARM platforms are the same except the Platform for InspectorUno.UWP is x86 and ARM respectively)

Building Uno Applications

We’ll go through each Uno application separately and I’ll endeavor to highlight typical pain points that developers face. If you’re using the pipeline_templates and you run into issues, message me on Twitter and I’ll help you out. Where there are common frustrations, I’ll work to improve the templates to make them easier to use.

Let’s start by creating a new pipeline in Azure DevOps. I’m not going to step through the pipeline wizard but for what we want to do, just select the appropriate source code respository and an empty YAML file. When you get to the YAML editor, enter the following.

trigger: none
resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.3.0
      endpoint: github_connection
  
variables:
  - group: 'Uno Build Variables'

There are three parts to this inital YAML. Firstly, we’re disabling the CI trigger whilst we’re configuring the build. As we’ll be committing changes to both the build pipeline and source code, it’s as well to only have builds triggered when you’re ready, otherwise you’ll be continually cancelling builds.

Next, the resources section is where we pull in the pipeline templates. In this case we’re targeting the v0.3.0 release to ensure the templates don’t change and break our builds in the future.

Lastly, we’re pulling in the Uno Build Variables variable group. You can pick whatever name you want for this group but it needs to match the name of the variable group defined under the Library tab on Azure DevOps.

Android – Build

Starting with Android we’re going to add stages to the YAML file (note you only need to include the stages element once and then add the individual stages by referencing the corresponding template). Here’s the Android build

stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_Android'
    build_android: $(android_enabled)
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-Uno-Android.aab'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

There are a few things you need to have setup for this build to work:

  • solution_filename – make sure the path to your solution file is correct, relevant to the root of your code repository.
  • $(version_prefix) – make sure the version_prefix variable exists in your variable group and has the format X.Y (eg 1.0)
  • $(android_keystore_filename) – make sure the android_keystore_filename variable exists in your variable group and that it’s value matches the Secure file name of the keystore file uploaded to the Secure files under the Library in Azure DevOps.
  • $(android_keystore_alias) – make sure the android_keystore_alias variable exists in your variable group and that it is the the alias that you specified when creating the keystore. This variable should be marked as private by clicking the lock beside it in the portal – this hides it both in the variable group editor in the portal and in the log files.
  • $(android_keystore_password) – make sure the android_keystore_password variable exists in your variable group and that it is the password you specified when creating the keystore. Again this should be marked as private.

Frustratingly, the Android build didn’t just work for a couple of reasons:

  • Firstly, there was an issue where one of the images has a file name that isn’t supported by the version of Visual Studio and/or Android tooling on the windows-latest build agent, resulting in the following error:
    2020-02-08T06:04:55.7036355Z ##[error]src\Apps\DotNet\Uno\InspectorUno\InspectorUno\InspectorUno.Shared\Assets\Square44x44Logo.targetsize-24_altform-unplated.png(0,0): Error APT0003: Invalid file name: It must contain only [^a-zA-Z0-9_.]+.
    I simply excluded the file Square44x44Logo.targetsize-24_altform-unplated.png from the shared project
  • Secondly, the build agent include the Android NDK but doesn’t specify the appropriate environment variables (eg ANDROID_NDK_HOME), resulting in the following error:
    2020-02-07T12:56:19.5428557Z ##[error]C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(2843,3): Error XA5101: Could not locate the Android NDK. Please make sure the Android NDK is installed in the Android SDK Manager, or if using a custom NDK path, please ensure the $(AndroidNdkDirectory) MSBuild property is set to the custom path.
    The solution here was for me to update the build-xamarin-android.yml to include these environment variables. You shouldn’t run into this error if you’re using v0.3.0 of the pipeline templates.

Android – Deploy

Here’s the YAML for deploying the Android application to AppCenter

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-Uno-Android.aab'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

Again there are a few things you need to check here but most importantly for this to work you need to setup an application in AppCenter. I created a new application

After creating the application, look at the url that appears in the browser. The piece highlighted in green is the value you need to set for the appcenter_organisation and the yellow is for the appcenter_applicationid.

You also need to make sure you setup a Service Connection between Azure DevOps and AppCenter (in this case it was given the name ‘AppCenterInspectorCI’ and assigned to the appcenter_service_connection property)

The other properties you need to check are:

  • depends_on – make sure this matches the stage_name property set on the android build stage
  • artifact_name, artifact_folder and application_package – make sure these match the corresponding value on the android build stage
  • secure_file_keystore_filename, keystore_alias and keystore_password – these should be set to the same as on the android build stage and are required so that the release process can extract and sign a fat apk from the aab file.

iOS – Build

Before we get into building your iOS application, let’s make sure you have both a signing certificate and a provisioning profile ready to go. If you have both of these, you can jump over the next two sections. These instructions can be stepped through on Windows, without the need for a Mac!

certificate

  • Install OpenSSL (Download from https://slproweb.com/products/Win32OpenSSL.html)
  • Open command prompt
  • Add OpenSSL to path (alternatively you can permanently add it to your Path environment variable if you want to have it accessible across all command prompts)
    set PATH=%PATH%;C:\Program Files\OpenSSL-Win64\bin\
  • Generate the signing request
    openssl genrsa -out ios_distribution.key 2048
    openssl req -new -key ios_distribution.key -out CertificateSigningRequest.certSigningRequest
  • Sign into the Apple Developer portal and go to Certificates (https://developer.apple.com/account/resources/certificates)
  • Click the + button to start the process of adding a new certificate
  • Select Apple Distribution from the list of certificate types and then Continue
  • Use the Choose File link to select the CertificateSigningRequest.certSigningRequest file you created in the previous steps, and then Continue
  • Important – Make sure you click the Download button to download your file. It’ll download as a file called distribution.cer unless you choose to rename it.
  • Move the distribution.cer file to the same folder that you’ve been working in
  • Complete the certificate creation from the command prompt (making sure you change PASSWORD to be a password of your choice)
    openssl x509 -in distribution.cer -inform DER -out ios_distribution.pem -outform PEM
    openssl pkcs12 -export -inkey ios_distribution.key -in ios_distribution.pem -out ios_distribution.p12 -passout pass:PASSWORD

With these steps completed you have a certificate file, ios_distribution.p12, which you can upload as a Secure File to Azure DevOps.

You’ll also need the signing identity, which we can get directly from the ios_distribution.pem that was generated along the way.
openssl x509 -text -noout -in ios_distribution.pem

Application Identifier

Before you can create a provisioning profile you need to setup an identity for your application in the Apple Developer portal. Go to the Identities tab and click on the + button to register a new application.

Select App IDs and then Continue

Enter both a Description and a Bundle ID. You may also need to select an App ID Prefix if you belong to a team. Next click Continue, followed by the Register button to complete the process of registering your application’s identity.

Note: Currently you will need to update the Info.plist file of your iOS application to set the CFBundleIdentifier to match the Bundle ID specified when creating the App ID.

Provisioning Profile

To create the provisioning profile, click on the Profiles tab in the Apple Developer portal. Click the + button to start the process of creating a new provisioning profile.

Select the Ad Hoc option under Distribution and then Continue.

Select the App ID that you previously created, then Continue

Select the certificate that needs to be used to sign the application, then Continue

Select the devices that will be able to install the application, then Continue

Give the provisioning profile a name and then click Generate.

Download the provisioning profile and add it as a Secure File in the Library in Azure DevOps. You’ll also need the provisioning profile id, which you can extract from the provisioning profile using any text editor. Open the provisioning profile and look for the UUID key; the string value immediately after it is the profile id.

Ok, we’re now ready for the build stage

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_iOS' 
    build_ios: true
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    ios_signing_identity: '$(ios_signing_identity)'
    ios_provisioning_profile_id: '$(ios_provisioning_profile_id)'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-Uno-iOS.ipa'

iOS – Deploy

The deploy stage is relatively simple

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-Uno-iOS.ipa'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-iOS-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

Windows – Build

The build stage for Windows (UWP) is

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_Windows'
    build_windows: true
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    uwpBuildPlatform: 'x86'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-Uno-Windows.msixbundle'
    windows_appxupload_name: 'Inspector-Uno-Windows.msixupload'

In order to build your Windows application you’re going to need a signing certificate. Microsoft has good documentation for creating the certificate which I would encourage you to follow. The important thing is to upload the certificate to the Secure files section under the Library tab on Azure DevOps and the corresponding password as a private variable. Damien Aicheh has a good tutorial on uploading certificates to Azure DevOps – don’t worry about the section on using the certificates as the Windows build template already does this for you.

Note: You also don’t need to worry about updating your package.appxmanifest so that the Publisher matches the Subject on the signing certificate. The Windows build template takes care of this too.

There are a couple of other parameters you’ll want to check off

  • uwpBuildPlatform – in this YAML this is only set to x86 for efficiency. If you’re planning to deploy your application to a range of devices, you should set this to x86|x64|ARM (this is the default value on the template so you can omit this property if you want to build for all three platforms)
  • $(windows_signing_certificate_securefiles_filename) – make sure the windows_signing_certificate_securefiles_filename variable exists in your variable group and that it’s value matches the Secure file name of the certificate (.pfx) file uploaded to the Secure files under the Library in Azure DevOps.
  • $(windows_signing_certificate_password) – make sure the windows_signing_certificate_password variable exists in your variable group and that it is password you specified when creating the certificate. This should be marked as private.

Note that the application package that we’re specifying here is an msixbundle. The Windows build pipeline has been upgraded to support both appx and msix bundles.

Windows – Deploy

The deploy stage for Windows (UWP) is

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-Uno-Windows.msixbundle'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-UWP-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

Pipeline Templates v0.3.0

As promised, here’s a quick summary of the changes in v0.3.0. Technically this should have been a major version increment as there were some breaking changes but since we’re not at v1 yet, I’ve just pushed a minor update.

Breaking Changes:

  • build-xamarin-android.yml – change android_bundle_name parameter to application_package (for consistency with deploy-appcenter.yml)
  • build-xamarin-ios.yml – change ios_ipa_name parameter to application_package (for consistency with deploy-appcenter.yml)
  • build-xamarin-windows.yml – change windows_bundle_name parameter to application_package (for consistency with deploy-appcenter.yml)

Other Changes:

  • build-xamarin-android.yml – added environment variables (ANDROID_NDK_HOME, ANDROID_NDK_PATH, AndroidNdkDirectory) so that the build process can locate the NDK. This is required for Android builds that enable AOT compilation.
  • build-xamarin-android.yml: android_manifest_filename property no longer required – template will search for AndroidManifest.xml
  • build-xamarin-windows.yml: windows_package_manifest_filename property no longer required – template will search for *.appxmanifest
  • build-xamarin-ios.yml: ios_plist_filename property no longer required – template will search for Info.plist
  • build-xamarin-windows.yml: application_package supports both appxbundle and msixbundle file types. You need to make sure the filename matches the type of bundle your application is setup to create.

Visual State Management with BuildIt.States and Uno

I’ve posted previously on using visual states in Uno and how they can be used to effectively manage the different visual layouts a page can take on. These may be changes in layout due to the application being resized, or perhaps due to different data loading states. I’ve recently created a Uno build of the … Continue reading “Visual State Management with BuildIt.States and Uno”

I’ve posted previously on using visual states in Uno and how they can be used to effectively manage the different visual layouts a page can take on. These may be changes in layout due to the application being resized, or perhaps due to different data loading states. I’ve recently created a Uno build of the BuildIt.States library, BuildIt.States.Uno. In this post I’m going to walk through using this library to help manage visual states from within your view model.

One of the topics that’s quite hot at the moment is whether XAML has had its day and whether the new coded UI techniques provide a better solution. I’m not going to go too far into this debate but one of the things I really like about XAML is the separation of the UI (declared in XAML) from its data representation (the ViewModel).

Whilst the data binding framework of both UWP and Xamarin.Forms works well for connecting properties on a ViewModel to attributes of a UI element, there is no way to connect visual states on the page, with some aspect of the ViewModel. This is where the BuildIt.States library kicks in.

Thinking about Visual States

Before we get into using the BuildIt.States library, lets start by thinking through the different states that our page can go through. For the purpose of this post we’re going to build a simple app with the following spec:

  • The app has a single page that has a button, “Load Data”
  • When the button is pressed, the button will be hidden and a loading indicator will be shown.
  • The app will attempt to load some data.
  • Loading data will randomly succeed or fail
  • When loading is complete (either succeed or fail) the loading indicator will be hidden, a message will be displayed indicating success or failure, and the Load Data button will be displayed again.
  • If the Load Data button is pressed again, the status of the previous attempt will remain on screen until the new attempt to load data has been completed.

An initial read of this spec would seem to indicate that there is a single set of states:

  • Not Loading
  • Loading
  • Loaded – Success
  • Loaded – Failed

However, when the button is pressed for the second time, there are two more states that need to be included : Loading (Loaded – Success) and Loading (Loaded – Failed).

An alternative way of thinking about the states is that there is a group of states that pertain to whether data is being loaded, and a different group of states that pertain to whether the page has data:

LoadingStates

  • NotLoading
  • Loading

These states control whether the button or the loading indicator is visible

DataStates

  • NoData
  • Data
  • DataFailedToLoad

These states control the visibility of the data, or in this case the message indicating success or failure of the data loading.

Visual States in XAML

Since the focus of this post isn’t on how to design a page in XAML I’ll skip over the steps involved in laying out the page. In summary, after creating a new Uno project (using the Uno project templates), I opened the MainPage.xaml in Visual Studio Blend. Whilst Blend is a long way from being the design-first tool it was once envisaged as, it does still have support for defining visual states, which is sorely missing in Visual Studio (and probably the only reason I still use Blend).

Using the design surface in Blend I’m able to layout the various TextBlock, Button and ProgressRing to build the simple UI for the app. I then use the States tool window to create two Visual State Groups (LoadingStates and DataStates) and the associated Visual States for showing the appropriate elements. The resulting XAML looks like the following:

<Page x:Class="BuildItWithStatesForUno.MainPage"
      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:local="using:BuildItWithStatesForUno"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="LoadingStates">
                <VisualState x:Name="NotLoading" />
                <VisualState x:Name="Loading">
                    <VisualState.Setters>
                        <Setter Target="LoadDataButton.(UIElement.Visibility)" Value="Collapsed" />
                        <Setter Target="LoadingProgress.(UIElement.Visibility)" Value="Visible" />
                        <Setter Target="LoadingProgress.(ProgressRing.IsActive)" Value="True" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="DataStates">
                <VisualState x:Name="NoData" />
                <VisualState x:Name="Data">
                    <VisualState.Setters>
                        <Setter Target="DataSuccessTextBlock.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="DataFailedToLoad">
                    <VisualState.Setters>
                        <Setter Target="DataFailedTextBlock.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
            <TextBlock x:Name="DataSuccessTextBlock"
                       Margin="20"
                       FontSize="40"
                       Foreground="Green"
                       Text="Data Loaded Successfully!"
                       TextAlignment="Center"
                       Visibility="Collapsed" />
            <TextBlock x:Name="DataFailedTextBlock"
                       Margin="20"
                       FontSize="40"
                       Foreground="Red"
                       Text="Data Failed to Load"
                       TextAlignment="Center"
                       Visibility="Collapsed" />
            <Grid Margin="50">
                <Button x:Name="LoadDataButton"
                        HorizontalAlignment="Center"
                        Click="LoadDataClick"
                        Content="Load Data"
                        FontSize="30" />
                <ProgressRing x:Name="LoadingProgress"
                              Width="50"
                              Height="50"
                              Foreground="Blue"
                              IsActive="False"
                              Visibility="Collapsed" />
            </Grid>
        </StackPanel>
    </Grid>
</Page>

Testing the Visual States

Before moving on I wanted to test that the Visual States work, so I wired up the Load Data button with an event handler so I could switch the visual states:

private async void LoadDataClick(object sender, RoutedEventArgs e)
{
    var loadTimeInMilliseconds = rnd.Next(1000, 10000);
    var success = loadTimeInMilliseconds % 2 > 0;
    VisualStateManager.GoToState(this, "Loading", true);
    await Task.Delay(loadTimeInMilliseconds);
    VisualStateManager.GoToState(this, "NotLoading", true);
    VisualStateManager.GoToState(this, success ? "Data" : "DataFailedToLoad", true);
}

Clearly this code isn’t production ready (string literals, codebehind, no error handling etc) but when I run the app, I can see the desired state changes. Here I’m just showing Android but since it’s Uno, it should work nicely on iOS, UWP and WASM too.

Visual States in Action (Gif captured and generated using Snagit from TechSmith)

ViewModel States

Now that we’ve defined the visual states, we need a way to both control and track the states in our ViewModel. In the same way that we can use data binding to update attributes of the visual elements on the page, we need a way to mirror visual states within our ViewModel. This is where we can make use of the StateManger from the BuildIt.States library.

In the following MainViewModel, a StateManager is created and setup using two different state groups. Rather than using string literals, we use an enum to define each state group. Note that each enum has a Base value, which reflects the default, or unset, state.

public enum LoadingStates
{
    Base,
    NotLoading,
    Loading
}

public enum DataStates
{
    Base,
    Data,
    DataFailedToLoad
}

public class MainViewModel : IHasStates
{
    private readonly Random rnd = new Random();

    public IStateManager StateManager { get; } = new StateManager();

    public MainViewModel()
    {
        StateManager
            .Group<LoadingStates>()
            .DefineAllStates()
            .Group<DataStates>()
            .DefineAllStates();
    }

    public async Task LoadData()
    {
        var loadTimeInMilliseconds = rnd.Next(1000, 10000);
        var success = loadTimeInMilliseconds % 2 > 0;

        await StateManager.GoToState(LoadingStates.Loading);
        await Task.Delay(loadTimeInMilliseconds);
        await StateManager.GoToState(LoadingStates.NotLoading);
        await StateManager.GoToState(LoadingStates.Loading);
        await StateManager.GoToState(success ? DataStates.Data : DataStates.DataFailedToLoad);
    }
}

I also need to update the codebehind in MainPage to create an instance of MainViewModel and then invoke the LoadData method when the Load Data button is clicked:

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();
    }
    private async void LoadDataClick(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadData();
    }
}

Connecting ViewModel to Visual States

Of course, if we run the code at this point, the state tracked within the MainViewModel don’t update the UI. To complete the loop, we need to attach another StateManager to the visual states defined on MainPage, and then bind the two StateManagers so that they can remain in sync. This is all done in the MainPage codebehind.

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;
    private IStateManager StateManager { get; } = new StateManager();

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();

        StateManager
            .Group<LoadingStates>()
            .DefineAllStates(this,this.LoadingStates)
            .Group<DataStates>()
            .DefineAllStates(this, this.DataStates);

        StateManager.Bind(ViewModel.StateManager);
    }
    private async void LoadDataClick(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadData();
    }
}

And there we go – now when you run the application and press the Load Data button the LoadData method on the MainViewModel will be invoked. As the states of the MainViewModel change, the StateManager and subsequently the visual states on the MainPage are updated.

Using the UWP SplitView on iOS, Android and WebAssembly with Uno

In this post we’re going to cover one of the basics of app navigation which is the use of the UWP SplitView. If you’re coming from iOS and Android development you might be thinking “huh, I don’t even know what that is.” Well the good news is that it’s actually something you’re already familiar with. … Continue reading “Using the UWP SplitView on iOS, Android and WebAssembly with Uno”

In this post we’re going to cover one of the basics of app navigation which is the use of the UWP SplitView. If you’re coming from iOS and Android development you might be thinking “huh, I don’t even know what that is.” Well the good news is that it’s actually something you’re already familiar with. Whether you’re used to an app that has a master-details layout, or one that uses a burger menu to display a flyout menu, these can both be implemented using the UWP SplitView.

One thing to be aware of is that the UWP SplitView is one of the basic controls that was added early in the UWP lifecycle. Since then there have been other controls added, such as the NavigationView and the MasterDetailsView (Windows Community Toolkit) that provide extended functionality and are worth exploring depending on the requirements of your project.

New Project – Cross-Platform App (Uno Platform)

Let’s get into it – we’re going to start with creating a new project based on the latest Uno Visual Studio Extension (updated at end of July – if you’re using an older version I would recommend updating). I’m also working with the Visual Studio 2019 (16.3 preview 1.0) as this provides the best support for working with WebAssembly.

Cross-Platform App using the Uno Solution Template

Once you’ve created your solution, I would highly recommend that you a) update NuGet references and b) go through each platform and make sure you can build and run the application. However, as at the time of writing do NOT update the Microsoft.Extensions.Logging.* packages – there’s a note in the csproj files that states “Note that for WebAssembly version 1.1.1 of the console logger required.” My recommendation is to leave these packages at v1.1.1 across all projects. For the Uno libraries I pick the latest prerelease versions but be warned that this occasionally backfires as you may get an unstable version – this is why it’s important to run each platform before proceeding!!

Note: One issue I’m currently seeing in the Uno template is that it generates assets (i.e. images) with names that include the scale factor eg scale-200. This is fine for UWP but fails to build for Android. I’ve gone through and just removed the scale factor from the filenames. You’ll need to rebuild your UWP project to make sure it picks up the filename changes correctly.

Note 2: When running the WebAssembly (aka WASM) project, make sure you select “Start without Debugging” in Visual Studio.

Note 3: When running the iOS build you may need to set the Deployment Target in the Info.plist. If you’re using the latest preview of Visual Studio it will pick up iOS 12.4 on the build agent which will cause you app to fail to deploy if you haven’t set the Deployment Target.

Adding the UWP SplitView

Now that we have our new application up and running, it’s time to add the SplitView control.

Design in Blend

For this we’re going to switch over to Blend – yeh, the product still exists and you’ll see in a minute why we’re going to be using it. To make the switch, right-click on the MainPage.xaml in the shared project and select Design in Blend.

Switching to Design in Blend

Switching to Blend will take a few seconds, particularly if this is the first time you’ve ever opened Blend. You’ll also see errors as the iOS and Android projects won’t open – ignore these and don’t worry about the upgrade log that will popup (these are irritating but can be safely ignored as they won’t impact your project).

Despite selecting Design in Blend from the MainPage.xaml, once in Blend, you’ll actually need to use the Solution Explorer window to open MainPage.xaml in the designer. Once you’ve opened MainPage.xaml, Blend should feel fairly familiar, even if this is the first time you’ve used it. This is because a lot of the windows are shared with Visual Studio – the separation between the products is a mix of legacy (don’t want to disrupt the tiny percentage of developers who still use it) and the optimising that design tasks will be done using Blend.

Blend Designer

Adding the SplitView

There are a number of ways to add the SplitView control into the MainPage. If you’re familiar with writing XAML it’s probably just easiest to add the SplitView element. However, to build familiarity with Blend, let’s add the SplitView using the designer. In the Assets window, enter “splitview” to locate the SplitView control. Use the mouse to drag the SplitView down to the Objects and Timeline window at the position in the hierarchy where you want it to appear. In this case we’re going to drop it inside the existing Grid – I typically have a Grid as the root of most pages to allow setting of background and other properties using a application-wide style, independently of what content is nested within the Grid.

Adding the UWP SplitView

Adding the SplitView this way will add it as the second child of the Grid, immediately following the existing TextBlock. As we want the SplitView to take up the entire space of the page, we’re going to move the TextBlock in the main content area by dragging the TextBlock into the Grid that is nested within the SplitView. The XAML of the page should now look like the following:

<Page
    x:Class="UwpSplitViewSample.MainPage"
    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:local="using:UwpSplitViewSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <SplitView>
            <SplitView.Pane>
                <Grid />
            </SplitView.Pane>
            <Grid>
                <TextBlock
                    Margin="20,20,20,20"
                    FontSize="30"
                    Text="Hello, world !" />
            </Grid>
        </SplitView>
    </Grid>
</Page>

The SplitView is made up of two distinct areas: the main content area and the pane that can be shown/hidden (i.e. the flyout). From this code we can see that the SplitView as two nested XAML elements which align to the SplitView areas. The first is the SplitView.Pane which defines the layout of the content that will appear within the pane/flyout of the SplitView. The second defines the layout of content to appear within the main content area of the SplitView, in this case a Grid with a TextBlock contained within it.

Adjusting Layout with Blend

I’ll repeat this: For those of us familiar with writing XAML, you’ll find that manually crafting XAML is by far the quickest way to add and edit elements. However, Blend provides a number of shortcuts for positioning elements that can come in handy. Some of which we’ll cover here.

Designing the SplitView Pane

For the purpose of this post, all we’re going to do is to add a StackPanel with a TextBlock and Button as children to both the SplitView.Pane and the SplitView. The TextBlock will be used to indicate what part of the SplitView it is (Pane or Content) and the Button will be used to Show and Hide the pane of the SplitView.

Let’s start with the SplitView.Pane which already has a Grid element. In the Objects and Timeline window, right-click on the Grid and select Change Layout Type, followed by StackPanel.

Change Layout Type to StackPanel

You should see the Grid change to StackPanel and remains selected in the Objects and Timeline window. Next, with the StackPanel still in focus, use the Assets window to locate the TextBlock and double-click on TextBlock to add a TextBlock as a child to the StackPanel. Repeat, this time for a Button.

We’re going to position the StackPanel in the centre of the SplitView pane. To do this, again keep the StackPanel selected in the Objects and Timeline window, we’re going to go to the Properties window and scroll down to the Layout section at set the HorizontalAlignment and VerticalAlignment properties to Center.

Centering the StackPanel

Whilst we’re using the Properties window, select the TextBlock and change the Text property to “Pane”, and then select the Button and change the Content property to “Hide Pane” (Note that unlike Xamarin.Forms that has a Text property, to set the text on a Button in UWP XAML you need to set the Content property).

The last thing we’re going to do with the XAML for the SplitView.Pane is to add an event handler to the Click event of the Button. With the Button selected in the Objects and Timeline window, go to the Properties window, and click on the lightning bold icon to switch from properties to events view. Locate the Click event and type the name of the event handler you want to create, in this case PaneButtonClick. When you press Enter the PaneButtonClick method will be created in the MainPage.xaml.cs code behind file and the XAML will be updated with the Click property.

Adding an Event Handler for the Click Event

For the moment, that’s where we’ll leave the XAML for the SplitView.Pane and we’ll implement the logic for the Click event handler shortly.

Designing the SplitView Content

The layout of the SplitView content is going to be very similar to that of the pane: A StackPanel with nested TextBlock and Button. However, we’re going to go about it slightly differently, considering the fact that we already have a TextBlock nested in a Grid in the content area. We’re going to start by wrapping the TextBlock in a StackPanel. To do this, right-click somewhere in the TextBlock on the designer and select Group Into, followed by StackPanel (this also works if you right-click on the TextBlock in the Objects and Timeline window).

After doing this the hierarchy of elements in the SplitView content will be Grid, StackPanel, TextBlock. However, if you look at the XAML you’ll notice that Blend has added some additional attributes, setting the Margin on the StackPanel and the Height and Width on the TextBlock. To remove these, we can just right-click the StackPanel and select Layout, followed by Reset All. Repeat this on the TextBlock.

From here we can repeat the remaining steps that we did for the SplitView pane:

  • Add a Button to the StackPanel
  • Set HorizontalAlignment and VerticalAlignment on the StackPanel to Center
  • Set Text on the TextBlock to “Content Area”
  • Set Content on the Button to “Show Pane”
  • Add an event handler called ShowPaneButtonClick to the Button Click event

The final XAML in MainPage.xaml should look similar to:

<Page x:Class="UwpSplitViewSample.MainPage"
      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:local="using:UwpSplitViewSample"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <SplitView>
      <SplitView.Pane>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
          <TextBlock Text="Pane"
                     TextWrapping="Wrap" />
          <Button Click="PaneButtonClick"
                  Content="Hide Pane" />
        </StackPanel>
      </SplitView.Pane>
      <Grid>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Orientation="Vertical">
          <TextBlock Text="Content Area" />
          <Button Click="ShowPaneButtonClick"
                  Content="Show Pane" />
        </StackPanel>
      </Grid>
    </SplitView>
  </Grid>
</Page>

DisplayMode Property on the UWP SplitView

The UWP SplitView has two properties that are worth exploring in a bit of detail and we’ll use these to control the behaviour, or should I say the layout, of the SplitView based on screen width. Let’s start with the easy one, which is the IsPaneOpen property. As you can imagine from the name, this property indicates whether the pane of the SplitView is currently open (i.e. displayed in full, rather than hidden or in compact mode), or not.

The second property worth looking at is the DisplayMode of the SplitView, and has the following four possible values:

  • Overlay – When the pane is visible it appears as an overlay across the content area. It doesn’t affect the size of the content area when it is shown/hidden.
  • Inline – When the pane is displayed it reduces the size of the content area by the width of the pane. As the pane is shown/hidden the width of the content area shrinks and grows accordingly.
  • CompactOverlay – Same as Overlay, except when the pane isn’t open, it still takes up space on the page equal to the CompactPaneLength property. Typically the compact view would show a list of icon buttons as a summary of the options available in the pane when it’s open.
  • CompactInline – Same as Inline, except when the pane isn’t open it still takes up space on the page equal to the CompactPaneLength property.

We’ll leave the use of the CompactOverlay and CompactInline options for the moment and focus on the other two options. The Overlay option is great for when the size of the application is narrow, for example on a mobile phone. The Inline option is better suited for when the application has sufficient width to allow the pane to be shown and there still to remain sufficient content area to be useful.

Using VisualState to Set DisplayMode

With this in mind, we’re going to use visual states to set the DisplayMode property based on the width of the application. For the purpose of this post we’re going to use a split point of 900, meaning that if the width of the application is greater than 900 the DisplayMode will be set to Inline. If it’s below 900 it’ll be set to Overlay.

To begin with, we’re going to set the default value of the DisplayMode to Compact by adding DisplayMode=”Compact” to the SplitView element. Next in Blend we’re going to add two visual states via the States window. In the States window, click the Add state group button and give the state group a name, SizeStateGroup.

Adding a Visual State Group

Next, click the Add state button twice to add two visual states and name them NarrowState and WideState. The actual names of the visual state group and the states are only for your benefit at this point, so you can name them according to what makes sense for you.

Adding a Visual State

Click on the WideState and you should see a red dot appear along side the state name. This indicates that Blend is in visual state editing mode. You should also see a red border appear around the main design area. Note that when you’re in visual state editing mode in Blend, any change you make via the tool windows (eg changing a property) will be tracked against the selected visual state.

Visual State Editing Mode

In visual state editing mode for the WideState, use the Properties window to change the DisplayMode to Inline. Next click the lightning icon button, Edit Adaptive Triggers, alongside the WideState visual state.

Adding an AdaptiveTrigger

In the Collection Editor: StateTriggers, set the MinWindowHeight to 0 and the MinWindowWidth to 900. This is adding a trigger which will return true when the width of the application exceeds 900. At this point the VisualStateManager will automatically switch to the WideState, without you having to explicitly run any code. Repeat the process of setting an adaptive trigger but this time for the NarrowState and set both MinWindowHeight and MinWindowWidth to 0.

The final XAML:

<Page x:Class="UwpSplitViewSample.MainPage"
      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:local="using:UwpSplitViewSample"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <SplitView x:Name="splitView"
               DisplayMode="Overlay">
      <SplitView.Pane>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
          <TextBlock Text="Pane"
                     TextWrapping="Wrap" />
          <Button Click="PaneButtonClick"
                  Content="Hide Pane" />
        </StackPanel>
      </SplitView.Pane>
      <Grid>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Orientation="Vertical">
          <TextBlock Text="Content Area" />
          <Button Click="ShowPaneButtonClick"
                  Content="Show Pane" />
        </StackPanel>
      </Grid>
    </SplitView>
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="SizeStateGroup">
       
        <VisualState x:Name="WideState">
          <VisualState.StateTriggers>
            <AdaptiveTrigger MinWindowHeight="0"
                             MinWindowWidth="900" />
          </VisualState.StateTriggers>
          <VisualState.Setters>
            <Setter Target="splitView.(SplitView.DisplayMode)" Value="Inline" />
          </VisualState.Setters>
        </VisualState>
        <VisualState x:Name="NarrowState">
          <VisualState.StateTriggers>
            <AdaptiveTrigger MinWindowHeight="0"
                             MinWindowWidth="0" />
          </VisualState.StateTriggers>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </Grid>
</Page>

Note: In UWP the order of the VisualState elements doesn’t matter as all the AdaptiveTriggers are evaluated and the best match is determined. For example if the application width is 1000, the conditions for the triggers for both NarrowState and WideState are met. However, the WideState is a better match since the width is > 900. For Uno (i.e. iOS, Android and WebAssembly), the order matters – the first trigger to match, wins. In the above XAML you’ll see the WideState before the NarrowState to make sure that if the application width is > 900, the WideState visual state is active.

Opening and Closing the UWP SplitView Pane

If you run the application at this point and adjust the width of the application the DisplayMode property will switch between Inline and Overlay. However, you won’t notice any change because we currently don’t have a way to open the pane. To do this we’ll add some code to the Click event handlers we created earlier, as follows:

private void PaneButtonClick(object sender, RoutedEventArgs e)
{
    splitView.IsPaneOpen = false;
}
private void ShowPaneButtonClick(object sender, RoutedEventArgs e)
{
    splitView.IsPaneOpen = true;
}

UWP SplitView In Action

With all the XAML and code in place, let’s see it in action on all various platforms.

UWP with Resizing Application
Opening the Pane on WASM
Auto-dismiss Flyout on Android

In this post we’ve covered off how easily you can build a responsive interface using Blend and the UWP SplitView. The fact that it then just works on iOS, Android and WebAssembly is a big bonus thanks to the Uno platform.

XAML Control Templates for Windows (UWP) and Platform.Uno

Recently there has been a lot of discussion about using code to declare the user interface of an app. Such as a recent post I did following the announcement of SwiftUI by Apple. In the Xamarin.Forms world the hashtag CSharpForMarkup has become the latest distraction. CSharpForMarkup encourages developers to move away from XAML to defining … Continue reading “XAML Control Templates for Windows (UWP) and Platform.Uno”

Recently there has been a lot of discussion about using code to declare the user interface of an app. Such as a recent post I did following the announcement of SwiftUI by Apple. In the Xamarin.Forms world the hashtag CSharpForMarkup has become the latest distraction. CSharpForMarkup encourages developers to move away from XAML to defining their layouts using C#. Whilst I’m not against this, I do feel that we’re all discussing the wrong problem. Whether you use code or XAML to define your layouts, Xamarin.Forms suffers from being tied to the underlying platform controls. Renderers, Effects and Visual are all mechanisms to compensate for not having XAML control templates for every control (more on templates).

Enter Platform.Uno and their mission to take the XAML framework from Windows (UWP) and adapt it to other platforms. As an alternative for building apps for iOS and Android, Platform Uno was moderately interesting. With their push to support WebAssembly, Platform.Uno opens up a world of opportunities for building rich applications for the web. Their recent example of publishing the Windows calculator to the web (https://calculator.platform.uno) is just the the start for this technology.

In this post we’ll walk through how the XAML templating system works and how to go about defining a Control Template to customise the behaviour of controls.

Note: In this post I’ll be using XAML so that I can use Blend to assist with writing XAML. You can choose to use C# if you prefer defining your layouts in code. The point of this post is to highlight the templating capabilities of UWP and Platform.Uno.

Lookless or Templated Controls

One of the foundations of XAML was the notion of a templated, or lookless, control. In his post “What does it mean to be ‘lookless’?” Dave observes that a lookless control is one where the UI is declared in XAML and the behaviour or functionality of the control is defined in code. My view is that a lookless control is one where the functionality of the control isn’t tied to a specific design. By this I mean that each aspect of the control should be defined using a template, thus making it a templated control. A developer using the control should be able to adjust any of the templates without affecting the functionality of the control.

What is a Button?

Let’s look at what this means in practice by examining a Button. If I asked you what you thought a Button in UWP is, you’d probably answer by writing the following element:

<Button Content="Press Me!" />

All you’ve done is declared a Button with content that says “Press Me!” that uses the default Button styles. You haven’t actually answered the question. So let’s start with a simple definition of a button.

A Button is an element that responds to the user tapping or clicking by performing some action.

However, in the context of UWP where a Button is an actual control, this definition is a little too vague. For example, the following Border/TextBlock combination matches the definition

<Border Background="Gray" Tapped="PressMeTapped">
    <TextBlock Text="Press Me!"/>
</Border>

Tapping on the Border does invoke the PressMeTapped event handler but is that all we mean when we say a Button. I would argue that one of the significant aspects to a Button is that it gives some visual cues to the user. By this I mean that when the user hovers their mouse over a button they can the mouse pointer changes, or the Button adjusts how it looks. Similarly, when the user taps or clicks on the Button they should get some visual feedback confirming their action. This feedback is what separates a Button from just simply a tap or click event handler that can be attached to any element.

What we’ve just observed is that the Button has a number of different visual states. In the context of a UWP button these are the Normal, PointerOver and Pressed states. Using the default Button style in the Light theme, these states are shown in the following image.

States in the Control Template for the UWP Button
UWP Button States

As part of extending our definition of a Button I was careful not to define what feedback, or change to layout, should be used for each state of the Button. UWP ships with Dark and Light themes and a set of default control styles that combine to give you the states shown above. However, the important thing to remember is that the visual appearance isn’t what defines a Button. Perhaps an updated definition might be something like:

A Button is an element that responds to the user tapping or clicking by performing some action. It has Normal, PointerOver, Pressed and Disabled visual states that can provide feedback and guide the user on how to interact with the control.

Content v Text Properties

Xamarin.Forms deviates from most other XAML platforms when it comes to defining elements and attributes. For example, instead of using a StackPanel, Xamarin.Forms has a StackLayout. Not only are they named differently, they also exhibit different behaviour. Similarly, the Button control has a Text property instead of a Content property. Whilst this might seem like a decision to simplify the Button control it highlights the limitation of Xamarin.Forms due to the lack of templating.

Let’s back up for a second and take a look at the Content property. In a lot of cases you might simply set the Content attribute to a string, such as the earlier example where I set the Content to “Press Me!”. However, to override the styling of the text, or add other content to the Button, I can set the Content property explicitly.

<Button>
    <StackPanel>
        <TextBlock Style="{StaticResource HeaderTextBlockStyle}"
                   Text="Press Me!" />
        <TextBlock Style="{StaticResource BodyTextBlockStyle}"
                   Text="or.... perhaps not" />
    </StackPanel>
</Button>

Note: As the Content property on the Button is annotated with the ContentPropertyAttribute, we don’t need to wrap the StackPanel in Button.Content tags.

Even though I’ve changed the content of the Button, I haven’t in anyway changed the way the Button functions. In fact, in this case I haven’t even changed how the Button changes appearance for the different states.

Using a Content Template

Before we get into customising the different Button states I just wanted to touch on the use of content templates. In the previous example I showed how the Content of the Button could be set to any arbitrary XAML. What happens if I want to reuse the content layout but just with different text? This is where the ContentTemplate property is useful.

The ContentTemplate property expects a DataTemplate which will be used to define the layout for the Content. At runtime the DataContext of the ContentTemplate will be set to the value of the Content property. Let’s see this in practice by moving the XAML from the Content property into the ContentTemplate.

<Button>
  <Button.Content>
    <local:ButtonData Heading="Press Me!" SubText="or.... perhaps not" />
  </Button.Content>
  <Button.ContentTemplate>
    <DataTemplate>
      <StackPanel>
        <TextBlock Style="{StaticResource HeaderTextBlockStyle}"
                   Text="{Binding Heading}" />
        <TextBlock Style="{StaticResource BodyTextBlockStyle}"
                   Text="{Binding SubText}" />
      </StackPanel>
    </DataTemplate>
  </Button.ContentTemplate>
</Button>

In this code block you can see that the Content property has been set to a new instance of the ButtonData class. The two TextBlock elements are subsequently data bound to the Heading and SubText properties.

public class ButtonData
{
    public string Heading { get; set; }

    public string SubText { get; set; }
}

What this shows is that we’ve separated the data (i.e. the instance of the ButtonData class) from the presentation (i.e. the DataTemplate that is specified for the ContentTemplate property). Now if we wanted to reuse the DataTemplate across multiple elements we can extract it as a StaticResource. The following code illustrates extracting the DataTemplate to a StaticResource, as well as updating the data binding syntax. The x:Bind syntax gives us strongly typed data binding. Meaning we get intellisense and better runtime performance as there are no reflection calls. Button1 and Button2 are properties on the page that return instances of the ButtonData class.

<Page.Resources>
    <DataTemplate x:Key="MyButtonTemplate"
                  x:DataType="local:ButtonData">
        <StackPanel>
            <TextBlock Style="{StaticResource HeaderTextBlockStyle}"
                       Text="{x:Bind Heading}" />
            <TextBlock Style="{StaticResource BodyTextBlockStyle}"
                       Text="{x:Bind SubText}" />
        </StackPanel>
    </DataTemplate>
</Page.Resources>
<Button Content="{x:Bind Button1}"
        ContentTemplate="{StaticResource MyButtonTemplate}" />
<Button Content="{x:Bind Button2}"
        ContentTemplate="{StaticResource MyButtonTemplate}" />

Custom Control Templates

So far we’ve seen how the Button control supports arbitrary Content and the use of ContentTemplates to reuse layouts across multiple controls. Neither of these can be used to adjust the visual appearance of the different Button states. In this section we’re going to start by looking at what properties you can adjust to tweak the existing states. We’ll then bust out Blend and start messing with the way a Button looks. All of this will be done without changing the functionality of the Button.

ThemeResources in the Control Template

In the previous section I added some arbitrary XAML to the Content of the Button. However, when I run the application I still see the default background gray. The background of the Button control is part of the default Template for the control. We’ll delve into this in a little more detail in the next sections. For the time being we’re going to adjust the colour of the background and round the corners on the border.

Adjusting the background of the Button when it’s in the Normal state can be done two ways. You can either specify the Background on the Button element itself, or you can override the ButtonBackground resource with the colour of your choosing.

<Page.Resources>
    <Color x:Key="ButtonBackground">DarkGreen</Color>
</Page.Resources>

<Button Content="{x:Bind Button1}"
        Background="Blue"
        ContentTemplate="{StaticResource MyButtonTemplate}" />
<Button Width="200"
        Height="200"
        HorizontalAlignment="Center"
        Content="{x:Bind Button2}"
        ContentTemplate="{StaticResource MyButtonTemplate}" />

At this point if you run the application up on UWP you’ll see that the initial colours for the two Button elements are blue and dark green respectivey. However, if you move the mouse over each of them you’ll see that they switch back to a grey background. This is because we’ve only adjusted the colour used to set the initial background colour. To adjust the colour for the PointerOver and Pressed states we need to adjust the appropriate resources, which are aptly called ButtonBackgroundPressed and ButtonBackgroundPressed. A full list of the overridable colours is available in the documentation for the Button control. Alternatively you can find the entire list of built in color resources in the ThemeResources.xaml file (more information here).

Button Colors in ThemeResources.xaml

In addition to changing the background colour, we also wanted to round the corners of the border that appears around the control when the user moves the mouse in closer. This can be done by simply setting the CornerRadius property to 10 on the Button element.

When we get to talking about visual states we’ll go through how properties on the Button control are modified with each visual state.

Changing the Control Template Layout

Sometimes it’s necessary to make changes that can’t be achieved by either setting the ContentTemplate or overriding the default colour resources. For example, say you wanted to add the Built to Roam logo as a watermark behind the content of the Button. To do this we need to override the Template for the Button. For this I’m going to use Blend as it’s particularly good at edit templates.

To begin editing the control template, locate the Button in the Objects and Timelines window. Right-click on the [Button] and select Edit Template, followed by Edit a Copy.

Editing a Copy of the Default Button Template

Next, you’ll be prompted to give the new template a name and specify where you want the template to be defined. When the new template is created it will include the default Button Template, thus allowing you to customise it. In this case the WatermarkButtonTemplate will be defined in the current document, limiting its availability to just the Button elements on the page.

Naming the New Control Template

Note: In this example we’re copying the default Button template. However, what gets copied into the XAML is actually the entire default Button style. This includes all the default property setters. The value of the Template property is set to a new instance of a ControlTemplate.

I’m not going to go through the changes to the template in detail but the summary is:

  • Wrap the ContentPresenter in a Grid called ContentFrame
  • Move Background, BackgroundSizing, BorderBrush, BorderThickness and CornerRadius from the ContentPresenter to ContentFrame
  • Adjust Visual States so that they reference ContentFrame for border and background properties. Leave any references to Foreground targetting the ContentPresenter element.
  • Add an Image inside the Grid with Source set to an image added to the Assets folder.
  • Set Opacity to 20% (0.2) and Stretch to Uniform on the Image.

Before:

<ControlTemplate TargetType="Button">
    <ContentPresenter
        x:Name="ContentPresenter"
        Padding="{TemplateBinding Padding}"
        HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
        VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
        AutomationProperties.AccessibilityView="Raw"
        Background="{TemplateBinding Background}"
        BackgroundSizing="{TemplateBinding BackgroundSizing}"
        BorderBrush="{TemplateBinding BorderBrush}"
        BorderThickness="{TemplateBinding BorderThickness}"
        Content="{TemplateBinding Content}"
        ContentTemplate="{TemplateBinding ContentTemplate}"
        ContentTransitions="{TemplateBinding ContentTransitions}"
        CornerRadius="{TemplateBinding CornerRadius}">
        <VisualStateManager.VisualStateGroups>                
        ...
        </VisualStateManager.VisualStateGroups>
    </ContentPresenter>
</ControlTemplate>

After:

<ControlTemplate TargetType="Button">
    <Grid
        x:Name="ContentFrame"
        Padding="{TemplateBinding Padding}"
        Background="{TemplateBinding Background}"
        BackgroundSizing="{TemplateBinding BackgroundSizing}"
        BorderBrush="{TemplateBinding BorderBrush}"
        BorderThickness="{TemplateBinding BorderThickness}"
        CornerRadius="{TemplateBinding CornerRadius}">
        <Image
            Opacity="0.2"
            Source="Assets/BuiltToRoamLogo.png"
            Stretch="Uniform" />
        <ContentPresenter
            x:Name="ContentPresenter"
            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
            AutomationProperties.AccessibilityView="Raw"
            Content="{TemplateBinding Content}"
            ContentTemplate="{TemplateBinding ContentTemplate}"
            ContentTransitions="{TemplateBinding ContentTransitions}"
            FontFamily="Segoe UI" />
        <VisualStateManager.VisualStateGroups>
        ...
        </VisualStateManager.VisualStateGroups>
    </Grid>
</ControlTemplate>

The net result is that the image is displayed on top of the background colour (which is set on the parent Grid) but beneath the content.

The important thing about defining this ControlTemplate is that it can be reused on any Button, irrespective of the Content that is being displayed. Again, these changes have affected the design of the Button but we still haven’t modified what happens when the user interacts with the Button.

Tweaking Control Template Visual States

The last set of changes we’re going to look at is how we modify the behaviour of the Button when the user does interact with it. In this case we’re going to look at the Pressed state. We’re going to change the default behaviour to slightly enlarge the Button when it’s in the Pressed state. To do this we need to modify the Pressed VisualState that is defined inside the ControlTemplate.

Using Blend we’ll step through adjusting the Pressed Visual State to expand the Button to 150% of it’s original size (i.e. a scale of 1.5 in both X and Y directions). To get started in the Objects and Timeline window, right click the Button element and select Edit Template, then Edit Current. This is similar to what we did previously but this time we already have a ControlTemplate in the XAML for the page.

Edit an Existing Control Template

Next, from the States window, select the Pressed state. If you click where the word Pressed is written you should see both the focus border around the Pressed state and a red dot appear alongside, indicating that Blend is in recording mode. You should also see a red border appear around the main design surface. If you look at the Button on the design surface it should appear like it’s been Pressed to illustrate what the Pressed state looks like.

Switching to the Pressed state

In the Properties window, locate the Transform section. Select the third icon in, which is for adjusting the Scale of the element. Set both X and Y values to 1.5. You should see a white square alongside each of the values indicating that you’ve explicitly set the value.

Adjusting the Scale Transform for X and Y

If you go back to the Objects and Timeline window you should see that under the ContentFrame there are entries for RenderTransform/ScaleX and RenderTransform/ScaleY to indicate that these properties have been modified for the currently selected visual state.

Visual State Properties

If you run the application now and click on the Button you should see that whilst the mouse button is depressed the Button moves to the Pressed state which shows the Button enlarged.

Enlarged Pressed State for Button

If you examine the change to the XAML, what you’ll see is that two Setters were added to the Pressed Visual State.

<VisualState x:Name="Pressed">
    <VisualState.Setters>
        <Setter Target="ContentFrame.(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Value="1.5" />
        <Setter Target="ContentFrame.(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Value="1.5" />
    </VisualState.Setters>
    <Storyboard>
        ... other existing animations ...
    </Storyboard>
</VisualState>

Animating Content Template State Transitions

The change we just made to the Control Template was to change the Scale X and Scale Y properties for the Pressed state. However, this is rather a jarring experience as there’s no transition or animation defined. The final step in this process is to add a transition both too and from the Pressed state.

In the States window, click the arrow with a plus sign and select the first item (i.e. *=>Pressed) to add a transition that will be invoked whenever the Button goes to the Pressed state, regardless of the initial state.

Adding Transition into Pressed State

If you look in the Objects and Timeline window you can now see a timeline that will define what animations are run during the transition.

Start of the Transition Timeline

Over in the Properties window, locate the Transform section and click on the empty square alongside the X property. Select Convert to Local Value, which will set the value to 1. The square will now appear as a sold white square. Repeat for Scale Y

Setting the Start of the Transition

Back in the Objects and Timeline window you should now see a keyframe marker appear at time 0. Drag the vertical orange line to 0.5 seconds.

End of Transition in Timeline

Now in the Properties window set the Scale X and Scale Y to 1.5, which is the end values for the transition. Thisshould match the values previously set for the Pressed state.

End Transition Values

Repeat this process for a transition for leaving the Pressed state. The transition should be the reverse with the Scale X and Y starting at 1.5 and ending at 1.

Transitions In and Out of Pressed State

And there you have it, an animated Pressed state.

Animated Pressed State in Control Template

The final XAML for the Transitions includes two Storyboards that defines the transition to and from the Pressed state.

<VisualStateGroup.Transitions>
    <VisualTransition GeneratedDuration="00:00:00"
                      To="Pressed">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ContentFrame"
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
                <EasingDoubleKeyFrame KeyTime="00:00:00"
                                      Value="1" />
                <EasingDoubleKeyFrame KeyTime="00:00:00.5000000"
                                      Value="1.5" />
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ContentFrame"
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)">
                <EasingDoubleKeyFrame KeyTime="00:00:00"
                                      Value="1" />
                <EasingDoubleKeyFrame KeyTime="00:00:00.5000000"
                                      Value="1.5" />
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </VisualTransition>
    <VisualTransition GeneratedDuration="00:00:00"
                      From="Pressed">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ContentFrame"
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
                <EasingDoubleKeyFrame KeyTime="00:00:00"
                                      Value="1.5" />
                <EasingDoubleKeyFrame KeyTime="00:00:00.5000000"
                                      Value="1" />
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ContentFrame"
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)">
                <EasingDoubleKeyFrame KeyTime="00:00:00"
                                      Value="1.5" />
                <EasingDoubleKeyFrame KeyTime="00:00:00.5000000"
                                      Value="1" />
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </VisualTransition>
</VisualStateGroup.Transitions>

As you can see from this walkthrough, the XAML templating system is relatively sophisticated. It allows for multiple levels of configuration or tweaking in order to get it looking just right.

In coming posts I’ll be talking more about what differentiates XAML and why it’s still being used across Windows, Xamarin.Forms and of course Platform Uno. If you’re looking for cross platform solutions, please don’t hesitate to contact me or anyone on the team at Built to Roam.