XAML Back to Basics #1: Data Context

How should I decide whether to use DataContext or Source?

XAML Basics Series Index Page

Last year I posted about a series of posts that Beatriz Stollnitz made on WPF/Silverlight that had been moved to GitHub. Unfortunately most of the samples don’t work out of the box with the latest version of Visual Studio but with some minor adjustments they’re easily fixed.

I took a fork of the repository and have started to a) update the wpf projects to work with VS2019 b) remove the Silverlight content and c) add equivalent examples that work cross platform using UWP and the Uno Platform.

Updated Code Samples

Importantly, as I go through each of the posts I’m going to reprint the majority of the original post with edits to bring them up to date with both WPF and UWP/Uno. I want to make sure the Beatriz is recognised as the original author of this content and that I just want to make sure her contributions live on to benefit the next generation of XAML developers.

Original post available on Github

How should I decide whether to use DataContext or Source?

The DataContext is one of the most fundamental concepts in Data Binding.

The Binding object needs to get its data from somewhere, and there are a few ways to specify the source of the data. In this post I talk about setting the Source property directly in the Binding vs inheriting a DataContext from the nearest element when traversing up in the tree. The other two alternatives are setting the ElementName and RelativeSource properties in the Binding object, but I will leave that for a future post.

For example, let’s assume we have the following data sources (GreekGod being a class defined in the code behind):

<Window.Resources>
    <local:GreekGod Name="Zeus" Description="Supreme God of the Olympians" RomanName="Jupiter" x:Key="zeus"/>
    <local:GreekGod Name="Poseidon" Description="God of the sea, earthquakes and horses" RomanName="Neptune" x:Key="poseidon"/>
</Window.Resources>

<StackPanel DataContext="{StaticResource poseidon}">
    <TextBlock TextContent="{Binding Source={StaticResource zeus}, Path=Name}"/>
    <TextBlock TextContent="{Binding Path=Description}"/>
    <TextBlock TextContent="{Binding Path=RomanName}"/>
</StackPanel>

The first TextBlock inherits the DataContext from the parent StackPanel and has a Source set in the Binding object too. In this case, Source takes priority, causing the TextBlock to bind to the Name property of the resource with key “zeus” – this displays “Zeus”.

The second TextBlock does not have a Source set directly in the Binding object, so it inherits the DataContext from the StackPanel. As you might guess, this binds to the Description property of the resource with key “poseidon”, displaying “God of the sea, earthquakes and horses”.

The third TextBlock should be straightforward – it displays “Neptune”.

Most data bound applications I see from users tend to use DataContext much more heavily than Source. My recommendation is to use DataContext when you need to bind more than one property to a particular source. When binding only one property, it may be better to use the Source attribute . The reason for this is ease of debugging – I would rather see all the information about the Binding in one place than search for the nearest DataContext to understand what is going on. In a small sample like the one above there is no big advantage, but in complex applications this could save you some time.

WPF Source Code

WPF

UWP/Uno Notes

In UWP it’s not very common to reference the Windows of the application directly. Instead you can define static resources (in this case the instances of the GreekGod class) at the Application, Page or even Control level. For example, to make the static resource available throughout a given page, we would define them as Page Resources.

<Page.Resources>
    <local:GreekGod Name="Zeus" Description="Supreme God of the Olympians" RomanName="Jupiter" x:Key="zeus"/>
    <local:GreekGod Name="Poseidon" Description="God of the sea, earthquakes and horses" RomanName="Neptune" x:Key="poseidon"/>
</Page.Resources>

UWP/Uno Source Code

UWP

Update 15th August 2020

Uno sample has been updated to v3 of Uno and supports iOS, Android, Windows and MacOS.

WinUI with Uno and WinUI for Desktop samples added.

WinUI with Uno and WinUI Desktop Source Code

WinUI Desktop

Don’t Judge XAML Based On Lines of Code

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

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

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

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

Xamarin.Forms Visual States

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

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

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

<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:XamlFlags"
    x:Class="XamlFlags.MainPage"
    x:Name="Page">
    <ContentPage.BindingContext>
        <local:MainPageViewModel />
    </ContentPage.BindingContext>
    <StackLayout
        BindableLayout.ItemsSource="{Binding Options}">
        <BindableLayout.ItemTemplate>
            <DataTemplate>
                <Frame
                    CornerRadius="4"
                    Padding="0">
                    <StackLayout
                        Orientation="Horizontal"
                        Padding="5"
                        BackgroundColor="White">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState
                    x:Name="IsEnabledAndSelected">
                    <VisualState.StateTriggers>
                        <StateTrigger
                            IsActive="{Binding Selected}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter
                            Property="BackgroundColor"
                            Value="DarkBlue" />
                        <Setter
                            Property="IsVisible"
                            Value="False"
                            TargetName="OptionsSelectedLabel" />
                        <Setter
                            Property="Label.TextColor"
                            Value="White"
                            TargetName="OptionsValueLabel" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState
                    x:Name="IsNotEnabled">
                    <VisualState.StateTriggers>
                        <StateTrigger
                            IsActive="{Binding IsNotEnabled}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter
                            Property="IsEnabled"
                            Value="false" />
                        <Setter
                            Property="BackgroundColor"
                            Value="DarkGray" />
                        <Setter
                            Property="Label.TextColor"
                            Value="LightGray"
                            TargetName="OptionsValueLabel" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
                        <Button
                            Text="Select"
                            Command="{Binding BindingContext.SelectTypeCommand, Source={x:Reference Page}}"
                            CommandParameter="{Binding .}" />
                        <Label
                            Text="✓"
                            TextColor="White"
                            x:Name="OptionsSelectedLabel"
                            FontSize="12"
                            HorizontalOptions="EndAndExpand"
                            VerticalOptions="Center"
                            IsVisible="false" />
                        <Label
                            Text="{Binding Value}"
                            TextColor="Black"
                            x:Name="OptionsValueLabel"
                            HorizontalOptions="EndAndExpand"
                            VerticalOptions="Center" />
                    </StackLayout>
                </Frame>
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </StackLayout>
</ContentPage>

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

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

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

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

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

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

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

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

UWP / WindowsUI / Uno

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

Optimised for Lines of Code

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

I will comment on a couple of things:

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

UserControl with Visual States

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

And the XAML for this is quite tidy.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ListView ItemsContainerStyle

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

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

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

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

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

VisualStates in different VisualStateGroups should not control the same property.

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

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

Summary

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

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

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

DotNetMaui (Xamarin.Forms) is Not a XAML Platform

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

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

DotNetMaui is Not JUST a XAML Platform

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

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

XAML as Declarative Markup

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

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

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

XAML Databinding

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

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

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

Model-View-Update (MVU)

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

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

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

Rendering Engine

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

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

Lookless Controls make a XAML Platform

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

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

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

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

Not JUST a XAML Platform

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

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

Picking Your Platform

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

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

Windows (UWP) Point Animations

A week or so ago I noticed a really slick piece of animation done as part of a Flutter app. Unfortunately I can no longer find the tweet as the account has been removed. However, it perked my interest enough to go playing with animations in XAML to see what I can do. Note that … Continue reading “Windows (UWP) Point Animations”

A week or so ago I noticed a really slick piece of animation done as part of a Flutter app. Unfortunately I can no longer find the tweet as the account has been removed. However, it perked my interest enough to go playing with animations in XAML to see what I can do. Note that I’m not trying to reproduce the original sample, just demonstrate that can be done with a bit of XAML and a bit of code.

Since I can’t show you the original animation, I’ll skip to the end and show you what I came up with. What you’re seeing in the following animated GIF is a drag animation that follows the mouse/touch pointer across the screen – imagine you’re using this to reveal a side menu or a different part of the UI of the app.

Ok, so how did I achieve this. Well, whilst I’d love to say that it was as easy as opening Blend, drawing a curve and then recording a storyboard, that is NOT what happened. In fact there’s almost no designer support for creating either the curve or the storyboard, mainly because it involves defining and then animation a path. However, with this said, I did do all the coding in Blend for Visual Studio and it did update the designer as I made changes.

If we take a look at a single frame, we can see what the shape is that we’re going to try to create.

Starting in the top left corner (0,0) we need a path that consists of

  • Horizontal line to (20,0)
  • Vertical line to (20,100)
  • A curve to (80,150)
  • A curve to (20,200)
  • Vertical line to (20,1000)
  • Horizontal line to (0,1000)
  • Vertical line to (0,0) to close the shape

In XAML this looks like

<Path x:Name="path"
        Stroke="Red"
        Fill="Red"
        StrokeThickness="2">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigureCollection>

                    <PathFigure StartPoint="0,0">
                        <PathFigure.Segments>
                            <PathSegmentCollection>
                                <LineSegment Point="20,0"
                                                x:Name="StartLine" />
                                <LineSegment Point="20,100"
                                                x:Name="TopLine"/>
                                <BezierSegment Point1="20,125"
                                                x:Name="Part1"
                                                Point2="80,135"
                                                Point3="80,150" />
                                <BezierSegment Point1="80,165"
                                                x:Name="Part2"
                                                Point2="20,175"
                                                Point3="20,200" />
                                <LineSegment Point="20,1000" x:Name="BottomLine"
                                                />
                                <LineSegment Point="0,1000"
                                                x:Name="EndLine" />
                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>
                </PathFigureCollection>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>

The next part is to animate this shape, which we can do by creating a storyboard and then moving each of the points on the shape. From the Objects and Timeline tool window, click the green + button to create a new Storyboard, which we’ve called our customStoryboard. Unfortunately this is where the designer support for animations runs out.

Dropping into XAML we can define the animations that make up the storyboard

<Page.Resources>
    <Storyboard x:Name="customStoryboard">
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point"
                                        Storyboard.TargetName="StartLine"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="StartLinePoint"
                                    Value="20,0"/>
        </PointAnimationUsingKeyFrames>
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point"
                                        Storyboard.TargetName="TopLine"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="TopLinePoint" 
                                    Value="20,100"/>
        </PointAnimationUsingKeyFrames>
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point1"
                                        Storyboard.TargetName="Part1"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="Part1Point1" 
                                    Value="20,125"/>
        </PointAnimationUsingKeyFrames>
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point2"
                                        Storyboard.TargetName="Part1"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="Part1Point2"
                                    Value="150,135"/>
        </PointAnimationUsingKeyFrames>
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point3"
                                        Storyboard.TargetName="Part1"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="Part1Point3" 
                                    Value="150,150"/>
        </PointAnimationUsingKeyFrames>
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point1"
                                        Storyboard.TargetName="Part2"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="Part2Point1" 
                                    Value="150,165"/>
        </PointAnimationUsingKeyFrames>
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point2"
                                        Storyboard.TargetName="Part2"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="Part2Point2" 
                                    Value="20,175"/>
        </PointAnimationUsingKeyFrames>
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point3"
                                        Storyboard.TargetName="Part2"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="Part2Point3" 
                                    Value="20,200"/>
        </PointAnimationUsingKeyFrames>
        <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Point"
                                        Storyboard.TargetName="BottomLine"
                                        EnableDependentAnimation="True">
            <LinearPointKeyFrame KeyTime="0:0:0.5"
                                    x:Name="BottomLinePoint" 
                                    Value="20,1000"/>
        </PointAnimationUsingKeyFrames>
    </Storyboard>
</Page.Resources>

After defining the storyboard, now you can return to the Objects and Timeline window and select the storyboard from the dropdown. Once selected you can position the cursor in the timezone.

At close to the 0.5 second mark (close to completion for most animations) the screen looks like this – definitely on the right track

Up to this point we’ve defined a set of line segments that make up a connected entity and a basic animation that simply adjusts the position of the points. What’s left to do is for the animation to track the mouse cursor or touch point.

We’re going to intercept and handle the Pointer_Moved event that is raised whenever a pointer moves across the page. In the event handler we need to calculate and apply the new position co-ordinates so that the animation can be amended.

The following code illustrate updating values (points) in the animation.

   public MainPage()
        {
            this.InitializeComponent();

            SizeChanged += MainPage_SizeChanged;
        }

        private void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Part1.Point3 = new Point(500, e.NewSize.Height / 2);
            Part2.Point2 = new Point(50, e.NewSize.Height - 100);
            Part2.Point3 = new Point(0, e.NewSize.Height);
            EndLine.Point = new Point(0, e.NewSize.Height);
        }

        private void Start_Animation(object sender, RoutedEventArgs e)
        {
            //myStoryboard.Begin();
        }
        const int StartWidth = 50;
        const int MaxDiffX = 400;
        const double HeightToWidthRatio = 1.5;
        private void path_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            // The location of the center is where the mouse/touch point is
            var newCenter = e.GetCurrentPoint(this).Position;
            var part1_point3 = newCenter;

            // Next determine the width of the shape
            var startX = Math.Max(StartWidth, newCenter.X - MaxDiffX);
            var width = newCenter.X - startX;

            // Calculate the height of the shape using the ratio
            var height = width * HeightToWidthRatio;

            // As we approach the other size, calculate how much space there is
            // and if close, reduce the width of the shape accordingly
            var remainingWidth = ActualWidth - newCenter.X;
            if (remainingWidth < width)
            {
                width = remainingWidth;
                startX = Math.Max(StartWidth, newCenter.X - width);
            }

            // The first line goes from 0,0 out along the X axis
            var start_point = new Point(startX, 0);
            // The last line goes from 0,<actual height> out along the X axis
            var end_point = new Point(startX, this.ActualHeight);


            // Calculate top and bottom of the shape
            var top = newCenter.Y - height / 2;
            var top_point = new Point(start_point.X, top);
            var bottom = newCenter.Y + height / 2;
            var part2_point3 = new Point(start_point.X, bottom);

            // Spread the remaining points in the shape
            var part1_point1 = new Point(start_point.X, top+ height * 0.25);
            var part1_point2 = new Point(newCenter.X, top+ height * 0.35);
            var part2_point1 = new Point(newCenter.X, top + height * 0.65);
            var part2_point2 = new Point(start_point.X, top+ height * 0.75);


            StartLinePoint.Value = start_point;
            TopLinePoint.Value = top_point;
            Part1Point1.Value = part1_point1;
            Part1Point2.Value = part1_point2;
            Part1Point3.Value = part1_point3;
            Part2Point1.Value = part2_point1;
            Part2Point2.Value = part2_point2;
            Part2Point3.Value = part2_point3;
            BottomLinePoint.Value = end_point;

            if (customStoryboard.GetCurrentState() != Windows.UI.Xaml.Media.Animation.ClockState.Stopped)
            {
                customStoryboard.Pause();
                customStoryboard.Resume();
            }
            else
            {
                customStoryboard.Begin();

            }
        }

And that’s it – a basic animation written in XAML, with a bit of code behind to check temp. It’s worth pointing out that the use of point based animations will result in smooth animations, rather than simply setting the points directly on the shape.

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.

Xamarin DevOps Snippets (aka Pipeline Templates)

Louis Matos has put together Xamarin Month with the topic of Code Snippets – Check out Louis’ blog for the full month of snippet. In this post I’m going to cover some code snippets that use Pipeline Templates in order to setup a Azure DevOps pipeline for your Xamarin application. Azure Pipeline Templates are a way … Continue reading “Xamarin DevOps Snippets (aka Pipeline Templates)”

Louis Matos has put together Xamarin Month with the topic of Code Snippets – Check out Louis’ blog for the full month of snippet. In this post I’m going to cover some code snippets that use Pipeline Templates in order to setup a Azure DevOps pipeline for your Xamarin application.

Azure Pipeline Templates are a way to define reusable components of YAML that can be shared across different pipelines and in fact across different repositories. Noting how difficult it was to setup even a basic pipeline to build and deploy a Xamarin application, the Pipeline Templates project was born to publish reusable templates that could easily be dropped into any pipeline.

Getting Started

In order to make use of templates, the first thing you need to do is to reference the pipeline_templates GitHub repository as a resource in you YAML build pipeline.

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.6.1
      endpoint: github_connection

It’s worth noting that the ref attribute is referencing the v0.6.1 release by specifying the tag. Alternatively you could reference any branch simply by changing the ref to a value similar to “refs/heads/nickr/bugfix” where the branch is nickr/bugfix. I would recommend referencing one of the tagged releases for stability of your build pipeline – from time to time we do make breaking changes, so if you’re referencing a branch, your build might start failing.

Build Templates

Android

To build the Android application, use the build-xamarin-android template – simply provide the necessary parameters.

stages:
- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Android'
    build_android_enabled: true
    # Version information
    full_version_number: '1.0.$(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: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Output information
    artifact_folder: 'artifacts'
    application_package: 'android.apk'

iOS

To build the iOS application, use the build-xamarin-ios template

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_iOS' 
    build_ios_enabled: true
    # Version information
    full_version_number: '1.0.$(Build.BuildId)'
    # Solution to build
    solution_filename: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Signing information
    ios_plist_filename: 'src/SnippetXamarinApp/SnippetXamarinApp.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: 'artifacts'
    application_package: 'ios.ipa'

Windows

To build the Windows application, use the build-xamarin-windows template. Technically this template should work with any UWP application.

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Windows'
    build_windows_enabled: true
    # Version information
    full_version_number: '1.0.$(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: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Output information
    artifact_folder: 'artifacts'
    application_package: 'windows.msix'

Deploy

Of course, once you’re done building your applications, you probably want to deploy the applications for testing. For this you can use the deploy-appcenter template. Note that you need to add a stage for each platform you want to deploy but you can use the same template as it knows how to deploy iOS, Android and Windows applications to AppCenter.

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    deploy_appcenter_enabled: true
    environment_name: 'AppCenter'
    # Build artifacts
    artifact_folder: 'artifacts'
    application_package: 'android.apk'
    # 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)

For a more detailed walk through of using the pipeline templates for building cross platform xamarin applications, please check out this post that covers the process end to end.

Call to Action: Contributions

The Pipeline Templates project is an open source project and I would love to get feedback and contributions from the community in order to provide more templates (not just for mobile).

Specifically, if anyone has built out the tasks necessary to deploy applications to each of the three stores, it would be great to create a template similar to the AppCenter template that targets the actual stores.

If anyone is familiar with both GitHub Actions and Azure Pipelines, it would be great to get someone to give me a hand to convert the existing templates for Azure DevOps across to GitHub Actions.

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

Improving Developer Experience with Multi-Targeted Visual Studio Projects

In my previous post on using multi-targeted cross platform projects I showed how you can add additional target frameworks to allow the addition of platform specific code to a library. One of the downsides of this approach (versus perhaps using a shared project) is that the library gets built once for every target framework that’s … Continue reading “Improving Developer Experience with Multi-Targeted Visual Studio Projects”

In my previous post on using multi-targeted cross platform projects I showed how you can add additional target frameworks to allow the addition of platform specific code to a library. One of the downsides of this approach (versus perhaps using a shared project) is that the library gets built once for every target framework that’s specified. In this post I’m going to show you how you can use the combination of a simple script and Solution Filters to optimise both the time it takes to load your solution in Visual Studio and the time it takes to compile your application.

Firstly, we’re going to adjust the Core project so that we can adjust which target frameworks are include based on a solution property called TargetsToBuild. If this is set to All, we’ll simply use the TargetFrameworks we defined in the previous post (i.e. .NET Standard, iOS, Android and Windows (UAP) when building on Windows). However, if TargetsToBuild is not equal to All, we’ll set the TargetFrameworks to be just the specific target framework that we’re interested in.

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup Condition=" '$(TargetsToBuild)' == 'All' ">
		<TargetFrameworks>netstandard2.0;xamarinios10;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>
	</PropertyGroup>

Note: I quite often will include netstandard2.0 in all of the TargetFrameworks to make sure the .NET Standard 2.0 build doesn’t break as we’re changing the Core library. This is a personal choice and you’ll definitely get better performance out of not having it build every time.

Ok, so the question is, where does TargetsToBuild get set. For this, we’re going to include a file called Directory.build.props in the solution folder. You can think of this as a file where you can define properties and include items and references across the entire solution. In this case, we’re going to set the contents to the following.

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

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

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

There’s two parts to this:

  • Firstly, if the Configuration is not set to Debug, the TargetsToBuild is always set to All. This is never modified, and is a fail safe to ensure all target frameworks are built when doing a Release build.
  • Secondly, the TargetsToBuild is set to the value held by TargetsToBuildDeveloperOverride when the Configuration is Debug. This variable is currently set to All.

The property TargetsToBuildDeveloperOverride essentially has four possible values: All, Android, iOS and Windows. Changing this value will adjust the target frameworks for the Core project. I would recommend restarting Visual Studio as the support for dynamically switching target frameworks in any given session isn’t there yet and doesn’t work 100% reliably.

The next piece to the puzzle is to use Solution Filters to limit which projects are loaded. By limiting which projects are loaded, your solution will load quicker and will also build faster (depending on what your build configuration settings are). To create a Solution Filter, simply unload the projects you don’t want loaded, then right-click on the solution node in Solution Explorer and click Save as Solution Filter. I’ve created the following solution filters:

  • MultiTargetSample.all.slnf: All projects are loaded
  • MultiTargetSample.Android.slnf: Core and Android head project loaded
  • MultiTargetSample.iOS.slnf: Core and iOS head project loaded
  • MultiTargetSample.Windows.slnf: Core and Windows head project loaded

The solution filter file, SLNF, is just a JSON file that lists which projects should be loaded. For example the MultiTargetSample.Windows.slnf file is as follows:

{
  "solution": {
    "path": "MultiTargetSample.sln",
    "projects": [
      "MultiTarget.Core\\MultiTarget.Core.csproj",
      "MultiTarget.Windows\\MultiTarget.Windows.csproj"
    ]
  }
}

The last piece of the puzzle is to combine the solution filters with the TargetsToBuildDeveloperOverride property. For example, if we want to be working on the Android application, we want to set the TargetsToBuildDeveloperOverride property to Android and then load the MultiTargetSample.Android.slnf solution filter. This is easily done using a couple of simple batch script.

Firstly, a generic script (LaunchVS.bat) that takes a parameter (the platform) and adjusts the TargetsToBuildDeveloperOverride property and then launches the appropriate solution filter.

powershell -Command "(gc Directory.build.props) -replace '[a-zA-Z]*', '%~1 ' | Out-File -encoding ASCII Directory.build.props"
start MultiTargetSample.%~1.slnf

Then, we have a script for each platform (eg LaunchVS.Windows.bat) which simply has the following command

LaunchVS.bat Windows

When you double-click on LaunchVS.Windows.bat in File Explorer, Visual Studio is launched with only the Core and Windows projects loaded. The Core library will only build the UAP target framework. This significantly improves both the solution load time and the build time, making working with MultiTargeted projects much easier.

Important: You might think that this isn’t important when you’re first starting out but I would highly recommend adding this to your solution from the get-go. It will save you hours of time both now and when your solution grows. Visual Studio does not handle large solutions with projects that have many target frameworks. Applying these changes will ensure Visual Studio continues to operate properly

Cross-Platform Libraries with Multi-Targeting for Xamarin, Uno and Windows Development

The story of cross platform development for Microsoft developers has been one marked by iteration after iteration of .NET framework fragmentation. With .NET 5 the promise is that we’ll return to having a single framework that will be used everywhere…. Sorry, what?? when did we ever have a single framework? For as long as I … Continue reading “Cross-Platform Libraries with Multi-Targeting for Xamarin, Uno and Windows Development”

The story of cross platform development for Microsoft developers has been one marked by iteration after iteration of .NET framework fragmentation. With .NET 5 the promise is that we’ll return to having a single framework that will be used everywhere…. Sorry, what?? when did we ever have a single framework? For as long as I can recall, developers working on mobile and/or IoT have always had a different framework to target (.NET CF, .NET MF, WinPhone (whatever that .NET version was called), UWP etc). However, one thing that hasn’t changed is the fact that in order to target different platforms, you inevitably have to write code that’s specific to that platform. Even during the announcement of MAUI, the new single project structure has specific files/code etc for iOS, Android, Windows etc. This problem isn’t going away, so let’s discuss how we deal with this issue today.

The scenario I’m going to cover in the post is relatively simple. I have three “head” or “target platform” projects (Windows, Android, iOS) that are going to reference a common library (Core). Whilst most of the Core library is common code that is compatible with .NET Standard 2.0, there’s some code that is specific to each platform. Rather than create a separate platform specific library, we can include this specific logic by setting up the Core library to be multi-targeted – meaning that a different DLL is generated for each supported platform. Luckily Visual Studio knows how to handle this, so you’re not left working out how to reference each DLL from the head projects.

Project Setup

Let’s get started. First up, we’ll create a blank solution. If you start with one of the head projects your solution will end up being called something list XXX.Windows.

Search for Solution and select Blank Solution
Name your solution

Next up, let’s create the three head projects: Windows (UWP), Android and iOS

Search for UWP and select the Blank App (Universal Windows) template
Name you Windows head project – I like to use the Windows suffix but uwp is also often used
Search for droid and select the Android App (Xamarin) template
Name your Android head project
Search for iOS and select the iOS App (Xamarin) template
Name your iOS head project

At this point, if you’re working on a Mac, you could also add a MacOS project template. For some reason this template isn’t available in Visual Studio for Windows (because no developer would ever want to create the MacOS project when working on windows …. I guess the same (lack of) logic is used on VS for Mac where you can’t create a UWP project).

Next up, we’ll create the common library that each of the head projects will reference.

Search for .net standard and select the Class Library (.NET Standard) template
Name the .NET Standard library

Common Code

So that we can see how this library will work, we’ll simply expose a string constant that can be referenced and displayed in each head project. We’ll rename the Class1.cs file to AppProperties.cs and allow Visual Studio to rename the corresponding class. We’ll then add a string constant as follows:

namespace MultiTarget.Core
{
    public class AppProperties
    {
        public const string AppName = "MultiTarget Sample";
    }
}

In each of the head projects we’ll add an text/label element to the UI and set the value to be AppProperties.AppName. Rather than having to use the Add Reference workflow (i.e. right click on the head project and click Add Reference), we’ll simply type the AppProperties class name and use the prompt in the code editor to Add reference to the MultiTarget.Core project.

Type Ctrl+. to get the code editor suggestions to appear.

Now we can run each of the head projects and see the same output on the screen which should say “MultiTarget Sample” (I don’t have an iOS device/simulator handy, so I don’t have a screenshot of it running on iOS).

Android – MultiTarget Sample
Windows – MultiTarget Sample

Platform Specific Code

The next step is to add platform specific code. In this case we’re going to augment the existing AppProperties class by adding a platform specific partial class. In Solution Explorer we can copy the AppProperties.cs and paste it into the same project. The copied file can then be renamed based on the platform. For example (see image below) the Windows specific code would be in a file called AppProperties.windows.cs.

Note: The actual name of the platform specific files doesn’t have any intrinsic meaning. As we’ll see in a minute, we will use the platform specific suffix to define which files will be included for each platform.

Inside the platform specific file we’ll add another constant called PlatformName. For example, the AppProperties.windows.cs includes a PlatformName constant that returns “Windows.”

using System;

namespace MultiTarget.Core
{
    public partial class AppProperties
    {
        public const string PlatformName = "Windows";
    }
}

We’re also going to update the AppName constant in the AppProperties.cs file to include the PlatformName. We also needed to add the partial keyword to the class definition.

namespace MultiTarget.Core
{
    public partial class AppProperties
    {
        public const string AppName = "MultiTarget Sample - " + PlatformName;
    }
}

After adding files for each platform, if you attempt to build the Core project it will fail with an error indicating that there are multiple definitions of the PlatformName constant. What we need to do is to only include one platform specific file at a time.

MultiTarget Project

Currently the Core project is configured with a single TargetFramework, netstandard2.0. This means that the only API that can be accessed are those defined by netstandard2.0. If we want to include platform specific code, or want to access platform specific APIs, we need to change the project to target multiple frameworks. Here’s the current project XML for the Core library

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

In order to target multiple frameworks, we can just change the TargetFramework element to TargetFrameworks, and add multiple frameworks, semicolon delimited (eg <TargetFrameworks>netstandard2.0;net451</TargetFrameworks> ). However, out of the box, this doesn’t support the various target framework monikers that are used across different platforms. To overcome this limitation, we need to change the Sdk attribute from “Microsoft.NET.Sdk” to “MSBuild.Sdk.Extras”. We also need to create a file called global.json in the solution folder with the following version information for the MSBuild.Sdk.Extra nuget package:

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

Note: Alternatively you can specify the version number in the Sdk attribute (eg Sdk=”MSBuild.Sdk.Extras/2.0.54″ ).

With these changes made, we can update the project file to include multiple target frameworks:

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup>
		<TargetFrameworks>netstandard2.0;xamarinios10;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>
</Project>

Note: we’ve included a conditional TargetFrameworks element so that the Windows (i.e. UWP) target platform is only include if the host OS is Windows. This is important because the UWP platform can’t be built using Visual Studio for Mac, so making this target framework conditional on the OS ensures the project can be loaded and built using Visual Studio for Mac.

At this point it’s worth noting that by including additional target frameworks, you’re effectively increasing the number of steps in the build. For example, when we build the Core library, we see that four DLLs are built – effectively increasing the built time by four times!!!

The new project format includes all files nested in the project folder by default. This means that we need to change the default behaviour for the platform specific files so that they’re excluded by default. Then we can specifically include files for each platform that is built. The project file now looks like this:

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup>
		<TargetFrameworks>netstandard2.0;xamarinios10;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>

	<ItemGroup>
		<Compile Remove="**\*.netstandard.cs" />
		<Compile Remove="**\*.droid.cs" />
		<Compile Remove="**\*.ios.cs" />
		<Compile Remove="**\*.windows.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) ">
		<Compile Include="**\*.netstandard.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('xamarinios')) ">
		<Compile Include="**\*.ios.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('monoandroid')) ">
		<Compile Include="**\*.droid.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('uap')) ">
		<Compile Include="**\*.windows.cs" />
	</ItemGroup>
</Project>

And that’s it – building the Core library will result in four DLLs, each one having a different value for PlatformName, and thus AppName. We didn’t need to make any changes to the head projects.

This technique for including platform specific code in a common library is used by popular frameworks such as ReactiveUI and MvvmCross, as well as Microsoft libraries such as Xamarin Essentials.

Source code

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

Add Build 2020 Schedule to Your Calendar

Last week Microsoft released the schedule for sessions for Build 2020. However, other than via the portal, there’s no easy way to get a list of sessions so that you can easily work out which sessions work for your time-zone and which sessions you want to attend. In order to solve my own dilemma of … Continue reading “Add Build 2020 Schedule to Your Calendar”

Last week Microsoft released the schedule for sessions for Build 2020. However, other than via the portal, there’s no easy way to get a list of sessions so that you can easily work out which sessions work for your time-zone and which sessions you want to attend. In order to solve my own dilemma of how to add the sessions to my calendar I built a quick service that would generate the appropriate ICS files. Actually my first option was a single ICS with all sessions, hence there are two options:

All Build 2020 Sessions in a Single ICS file

All Build 2020 Sessions as Individual ICS files

Here’s how the whole thing went down – mid-last week I was wondering whether there was an easy way to add the schedule to my Outlook calendar. At this point the only schedule that had been announced was a high level list of just some of the sessions eg:

Given there’s not that many sessions, it would have been quick enough for me to add them individually to Outlook. But no, the developer in me went hunting for how to automate this. Opening Fiddler I saw that there was indeed an API that was being called at endpoints beginning with https://api.mybuild.microsoft.com. However, at this point there weren’t any calls to retrieve sessions, speakers etc, the only calls were to the /chrome and /settings endpoints which didn’t really reveal much.

Thinking that I wouldn’t be able to get much further I tried a last ditch effort of searching for other uses on the web for api.mybuild.microsoft.com. I guessed there wouldn’t be any direct hits, other than for the mybuild.microsoft.com portal, but I figured that given the way that search engines look for similar matches, something might turn up. Sure enough, the api url is not to dissimilar to the one used for prior events eg https://api.mybuild.techcommunity.microsoft.com. This lead me to a pretty sophisticated PowerShell script that Michel de Rooij had put together for scraping session content for prior events.

Knowing that Microsoft typically uses the same vendor for things to make it easier year-on-year, it’s no surprising that the api described by Michel’s PowerShell script was again being used for Build 2020.

I quickly used Json2Csharp QuickType (I can never remember this name but the service is great!) to generate the classes required to interact with the API. I can’t say that I engineered the ASP.NET Core 3.1 Web API service to be particularly efficient, mainly because I predicted that I was going to put both Azure CDN and CloudFlare in front of it. It basically pulls requests the entire list of sessions and holds them in a static variable. The only caching being that if the service is requested again whilst the static variable hasn’t been collected, it will return the previously retrieved list of sessions (do NOT follow this architecture for a production application).

I used iCal.NET to generate the ICS files – initially one monster ICS which has all the sessions but then I realised that it might be better to have individual ICS files that were downloaded as a Zip file. The ICS file(s) also contain session speakers (with links to their profile and/or their company, where available) and a link to the session for registering and attending.

Next I published the ASP.NET Core service to a new Azure App Service. As this was a personal project I opted for a free Azure App Service Plan. Of course, this will not scale well, since it’s really designed for development and testing purposes. However, given that I’m generating ICS files that aren’t likely to change frequently, I figure I’m going to protect my service so that it doesn’t get slammed.

The protection comes in two forms:

  • Firstly, I added an Azure CDN endpoint in front of the app service – this will cache the output of my service (level 1)
  • Secondly, I added Cloud Flare for two reasons. This gives me a nice friendly url (ie build2020.builttoroam.com) that I can share, without exposing either my Azure CDN endpoint or my app service url. I also enabled the proxying option which means that CloudFlare also caches the output of my service (level 2).

The upshot is that even if a massive number of people download the ICS file(s), my service shouldn’t even need to wake up. This is reflected by the analytics from my Azure App Service that shows almost zero out-bound data over the last 24 hours.

Hopefully the ICS files (Single ICS file or Individual ICS files) helps you build your schedule for Build 2020. Look forward to seeing everyone there, virtually of course!

Pipeline Templates: Templates for Downloading and Caching

Earlier this year I published a pre-release version of an Azure DevOps extension that wrapped the ApkTool for extracting and re-packing Android APK files. Having spent more time working with Azure Pipelines, I realised that most of what I was doing with the extension could actually be done with one or more templates. Today I … Continue reading “Pipeline Templates: Templates for Downloading and Caching”

Earlier this year I published a pre-release version of an Azure DevOps extension that wrapped the ApkTool for extracting and re-packing Android APK files. Having spent more time working with Azure Pipelines, I realised that most of what I was doing with the extension could actually be done with one or more templates. Today I started the process of creating the templates… and unfortunately got side tracked in over engineering a couple of the templates. The upshot is that the Pipeline Templates library now has the following new templates (there’s no release yet but you can reference them off the master branch if you want to use them today).

  • Download-Url-To-File – As the name suggests this template can be used to download a url (source_url) to a specified file (target_path). The target_path parameter can be an absolute or relative path, and you can specify the overwrite_existing parameter if you want to force the url to be downloaded even if the file already exists.
  • Check-File-Exists – Again, the name says what’s in the box – this template checks to see if the specified file (path_to_check) exists. The response can either be an output variable (specified using the file_exists_variable parameter) or an error can be thrown (which will break the current build)
  • Download-And-Cache – Rather than having to download files every time a build is run, the files can be cached and reloaded for each build. This template makes this easy.
  • ApkTool-Install – This downloads and caches the two files required to run the apktool (apktool.jar and apktool.bat) on Windows.

Once I get to the point where I’m able to replicate the functionality of the Apktool extension I’ll publish these updates as a new release. In meantime, if there are any other templates you’d like to see, feel free to create an issue on the Pipeline Templates GitHub repo, or drop me a comment

Shadows in Windows (UWP) XAML Applications – Part 4 – Custom Shadows

In part 2 of this series of posts on Shadows in Windows (UWP) XAML Applications (parts 1, 1b, 2 and 3) we saw that the composition APIs could be used to generate a DropShadow. However, what wasn’t immediately clear is that this mechanism only works for a limited set of controls, namely Shape (including Ellipse, … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 4 – Custom Shadows”

In part 2 of this series of posts on Shadows in Windows (UWP) XAML Applications (parts 1, 1b, 2 and 3) we saw that the composition APIs could be used to generate a DropShadow. However, what wasn’t immediately clear is that this mechanism only works for a limited set of controls, namely Shape (including Ellipse, Line, Path, Polygon, Polyline, Rectangle), Image and TextBlock. This is because these controls are the only ones that expose the GetAlphaMask method (and frustratingly this method isn’t even part of a common interface that the controls share). This challenge has previously been pointed out by Mike Taulty in his post about creating shadows, back in 2016. I’m still amazed that there doesn’t seem to be any improvement on this over 4 years later (and no, the ThemeShadow, as I pointed out in my post, isn’t the solution to this problem).

So how do we create a shadow for elements that don’t have the GetAlphaMask? Well the answer presented by Mike was to generate an image from the element, and then use a CompositionBrush generated from the image in order to define the mask for the DropShadow. To do this, we’ll start by adding the CompositionImageBrush to our application (code taken from Mike’s post).

public class CompositionImageBrush : IDisposable
{
    CompositionGraphicsDevice graphicsDevice;
    CompositionDrawingSurface drawingSurface;
    CompositionSurfaceBrush drawingBrush;

    public CompositionBrush Brush => drawingBrush;

    private CompositionImageBrush()
    {
    }

    private void CreateDevice(Compositor compositor)
    {
        graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(
            compositor, CanvasDevice.GetSharedDevice());
    }

    private void CreateDrawingSurface(Size drawSize)
    {
        drawingSurface = graphicsDevice.CreateDrawingSurface(
            drawSize,
            DirectXPixelFormat.B8G8R8A8UIntNormalized,
            DirectXAlphaMode.Premultiplied);
    }

    private void CreateSurfaceBrush(Compositor compositor)
    {
        drawingBrush = compositor.CreateSurfaceBrush(drawingSurface);
    }

    public static CompositionImageBrush FromBGRASoftwareBitmap(
        Compositor compositor,
        SoftwareBitmap bitmap,
        Size outputSize)
    {
        CompositionImageBrush brush = new CompositionImageBrush();

        brush.CreateDevice(compositor);

        brush.CreateDrawingSurface(outputSize);
        brush.DrawSoftwareBitmap(bitmap, outputSize);
        brush.CreateSurfaceBrush(compositor);

        return (brush);
    }

    private void DrawSoftwareBitmap(SoftwareBitmap softwareBitmap, Size renderSize)
    {
        using (var drawingSession = CanvasComposition.CreateDrawingSession(drawingSurface))
        using (var bitmap = CanvasBitmap.CreateFromSoftwareBitmap(drawingSession.Device, softwareBitmap))
        {
            drawingSession.DrawImage(bitmap,
                new Rect(0, 0, renderSize.Width, renderSize.Height));
        }
    }
        
    public void Dispose()
    {
        drawingBrush.Dispose();
        drawingSurface.Dispose();
        graphicsDevice.Dispose();
    }
}

Next, we’re going to create an extension method for UIElement that will either return the result from GetAlphaMask for those elements where it’s defined, or it will return a brush generated from taking an image shapshot of the element.

public static class UIElementHelpers
{
    public static async Task<CompositionBrush> ShadowAlphaMask(this UIElement uiElement)
    {
        CompositionBrush mask = null;
        if (uiElement is Shape shapeElement)
        {
            mask = shapeElement.GetAlphaMask();
        }
        else if (uiElement is Image imageElement)
        {
            mask = imageElement.GetAlphaMask();
        }
        else if (uiElement is TextBlock textElement)
        {
            mask = textElement.GetAlphaMask();
        }
        else if (uiElement is FrameworkElement frameworkElement)
        {
            var gridVisual = ElementCompositionPreview.GetElementVisual(uiElement);
            var elementVisual = gridVisual.Compositor.CreateSpriteVisual();
            elementVisual.Size = uiElement.RenderSize.ToVector2();
            var bitmap = new RenderTargetBitmap();
            await bitmap.RenderAsync(
                uiElement,
                (int)frameworkElement.ActualWidth,
                (int)frameworkElement.ActualHeight);
            var pixels = await bitmap.GetPixelsAsync();
            using (var softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(
                pixels,
                BitmapPixelFormat.Bgra8,
                bitmap.PixelWidth,
                bitmap.PixelHeight,
                BitmapAlphaMode.Premultiplied))
            {
                var brush = CompositionImageBrush.FromBGRASoftwareBitmap(
                    gridVisual.Compositor,
                    softwareBitmap,
                    new Size(bitmap.PixelWidth, bitmap.PixelHeight));
                mask = brush.Brush;
            }
        }
        return mask;
    }
}

Lastly we need to modify the code for generating the DropShadow to use this new extension method.

private async void Grid_Loaded(object sender, RoutedEventArgs e)
{
    var shadowColor = (Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush).Color;
    var compositor = ElementCompositionPreview.GetElementVisual(Host).Compositor;

    // Create the drop shadow
    var dropShadow = compositor.CreateDropShadow();
    dropShadow.Color = shadowColor;
    dropShadow.BlurRadius = 16;
    dropShadow.Opacity = 20.0f;

    // Use the shape of the element (in this case ShadowContent) to 
    // control shape of shadow
    var mask = await ShadowContent.ShadowAlphaMask();
    dropShadow.Mask = mask;

    // Set the shadow on the visual
    var spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Size = new Vector2((float)Host.ActualWidth, (float)Host.ActualHeight);
    spriteVisual.Shadow = dropShadow;
    ElementCompositionPreview.SetElementChildVisual(Host, spriteVisual);
}

Previously we were generating the shadow for an element called Rectangle2 – we’ve updated the code to use ShadowContent and the XAML now looks like.

<Grid Margin="50"
        Height="200"
        Width="200"
        VerticalAlignment="Bottom"
        HorizontalAlignment="Left">
    <Grid x:Name="Host" />
    <StackPanel x:Name="ShadowContent"
                Background="Pink">
        <Rectangle x:Name="Rectangle2"
                    Fill="Turquoise" />
        <TextBox />
        <Button Content="Press me!" />
    </StackPanel>
</Grid>

Ok, so I guess the only thing let to do is to run the application and show you the output.

And there you have it – a nice, easy to use extension method can can return a brush from any element that can be used to mask the DropShadow.

Shadows in Windows (UWP) XAML Applications – Part 3 – DropShadowPanel

In Part 2 of this series I talked about how to create your own shadow using the DropShadow class. I mentioned that whilst it was easy enough to do, the DropShadow had to be created in code, rather than being applied in XAML. Well the good news is that the team building the Windows Community … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 3 – DropShadowPanel”

In Part 2 of this series I talked about how to create your own shadow using the DropShadow class. I mentioned that whilst it was easy enough to do, the DropShadow had to be created in code, rather than being applied in XAML. Well the good news is that the team building the Windows Community Toolkit say the same issue and have produced the DropShadowPanel which can be used to apply a shadow via XAML.

Let’s again use our two Rectangle series. I’m going to jump straight to the XAML that shows how to use the DropShadowPanel, complete with rounded corners and theme colour support.

<controls:DropShadowPanel BlurRadius="10"
                            ShadowOpacity="1"
                            Color="{ThemeResource ApplicationForegroundThemeColor}"
                            Margin="50"
                            VerticalAlignment="Bottom"
                            HorizontalAlignment="Left"
                            IsMasked="True">
    <Rectangle x:Name="Rectangle2"
                Fill="Turquoise"
                RadiusX="40"
                RadiusY="40"
                Height="200"
                Width="200" />
</controls:DropShadowPanel>

It’s worth noting here that the DropShadowPanel accepts a Color for the shadow, rather than a brush. As such I’ve had to register an additional theme resources.

var foregroundBrush = this.Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush;
Resources["ApplicationForegroundThemeColor"] = foregroundBrush.Color;

And then of course we want to see what this looks like in action.

Note that the DropShadowPanel works in both light (left part of image) and dark (right part of image) themes

One last thing to note before I wrap up this post. You’ll notice in the above XAML that there is an attribute IsMasked that is set to True. This is actually the default value for this property, so could be excluded. However, I wanted to make note of it because it can be toggled to adjust the behaviour of the shadow. For example if I set this attribute to false, we see the following shadow being cast.

Clearly, in this scenario we want to set IsMasked to true so that we get the nice rounded corners in the shadow.

For anyone wanting to quickly apply a shadow in XAML, the DropShadowPanel from the Windows Community Toolkit has you covered.

Shadows in Windows (UWP) XAML Applications – Part 2 – DropShadow

Following Part 1 – ThemeShadow (and Part 1b – Lists) in this post we’re going to look at a very simple example of creating your own shadow. I’m going to reuse my simple example of two overlapping rectangles. The goal is to: Add a shadow around the bottom-left rectangle The shadow should elevate the rectangle … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 2 – DropShadow”

Following Part 1 – ThemeShadow (and Part 1b – Lists) in this post we’re going to look at a very simple example of creating your own shadow. I’m going to reuse my simple example of two overlapping rectangles.

The goal is to:

  • Add a shadow around the bottom-left rectangle
  • The shadow should elevate the rectangle off the background
  • The shadow should elevate the rectangle away from the other rectangle
  • The shadow should handle changing the corner radius to allow for rounded corners
  • The shadow should handle changes to the system theme (i.e. dark mode)

Ok, so let’s see how we can achieve this by creating our own DropShadow. As with the ThemeShadow there are two elements that participate in the creation of the shadow effect. There’s the item casting the shadow (in this case Rectangle2) and then there’s the surface where the shadow needs to be rendered. In this case, since we want the shadow to appear around the edge of Rectangle2, we’re going to create an additional Grid (named Host in the following XAML) that matches the size of Rectangle2:

<Grid Height="400"
        Width="400"
        Loaded="Grid_Loaded"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Rectangle x:Name="Rectangle1"
                Margin="50"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Top"
                HorizontalAlignment="Right" />
    <Grid Margin="50"
            Height="200"
            Width="200"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left">
        <Grid x:Name="Host" />
        <Rectangle x:Name="Rectangle2"
                    Fill="Turquoise" />
    </Grid>
</Grid>

To create and apply the shadow we’re handling the Loaded event on the parent Grid. The following logic creates the DropShadow, uses Rectangle2 as a mask and then attaches the DropShadow to the SpriteVisual for the Host Grid.

private void Grid_Loaded(object sender, RoutedEventArgs e)
{
    var shadowColor = (Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush).Color;
    var compositor = ElementCompositionPreview.GetElementVisual(Host).Compositor;
            
    // Create the drop shadow
    var dropShadow = compositor.CreateDropShadow();
    dropShadow.Color = shadowColor;
    dropShadow.BlurRadius = 16;
    dropShadow.Opacity = 20.0f;

    // Use the shape of the element (in this case Rectangle2) to 
    // control shape of shadow
    var mask = Rectangle2.GetAlphaMask();
    dropShadow.Mask = mask;
            
    // Set the shadow on the visual
    var spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Size = new Vector2((float)Host.ActualWidth, (float)Host.ActualHeight);
    spriteVisual.Shadow = dropShadow;
    ElementCompositionPreview.SetElementChildVisual(Host, spriteVisual);
}

The result of this is shown in the following image

Now let’s try rounding the corners of Rectangle2

<Rectangle x:Name="Rectangle2"
            Fill="Turquoise"
            RadiusX="40"
            RadiusY="40" />

Here’s how it looks

This is looking really nice but what about the dark mode support?

Even in dark mode, we’re still seeing the shadow being cast both against the background but also on Rectangle1. Going back to the XAML and code that creates this effect, it’s important to note that the background is set using the ApplicationPageBackgroundThemeBrush and that the color that’s set for the shadow is based on the ApplicationForegroundThemeBrush. Both these brushes are theme aware, meaning that as the device switches between light and dark mode, the brushes have the appropriate color. This means that there’s little else we need to do in order to support dark mode when creating the shadow.

The upshot of creating a shadow this way is that it’s relatively straight forward but does require custom logic to be written – this is less than ideal if you want to apply shadows easily in xaml for example.

In subsequent posts we’ll look at other options for applying shadows that hopefully will mean less custom code.

Shadows in Windows (UWP) XAML Applications – Part 1b – ThemeShadow in Lists

Originally, I had planned that the next post was going to talk about making use of the Shadow property and creating a custom shadow. However, following part 1 there was a bit of dialog online talking about the use of shadows in lists. I figured that it shouldn’t be too hard to extend my example … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 1b – ThemeShadow in Lists”

Originally, I had planned that the next post was going to talk about making use of the Shadow property and creating a custom shadow. However, following part 1 there was a bit of dialog online talking about the use of shadows in lists. I figured that it shouldn’t be too hard to extend my example to use the ThemeShadow for items in a list.

Let’s start with a simple list of 1000 items which we’ll be displaying using a ListView – I’m sure there’s a better way to generate random list items but I was just hacking around, so didn’t feel like engineering anything more fancy.

public IList<string> StaticItems { get; } = BuildStaticItems();

private static List<string> BuildStaticItems()
{
    var list = new List<string>();
    const int max = 1000;
    for (int i = 0; i < max; i++)
    {
        list.Add($"{max} items - {i}");
    }
    return list;
}

Next up we need the XAML layout for the ListView:

<Grid x:Name="RootGrid">
    <Grid x:Name="ParentBackgroundGrid" />

    <ListView ItemContainerStyle="{StaticResource ThousandItemContainerStyle}"
                ItemsSource="{x:Bind StaticItems}">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="x:String">
                <TextBlock Text="{x:Bind }"
                            Margin="6" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

There are a couple of things to note here:

  • The ItemsSource is bound to the StaticItems property from the previous code block
  • The ItemTemplate is just a simple TextBlock with a Margin set to indent the text from the edge of the item template.
  • There is a ParentBackgroundGrid priors to the ListView which will render beneath the ListView and will be the surface where the shadows are rendered onto.
  • Lastly this snippet defines an ItemContainerStyle called ThousanItemContainerStyle which we’ll go into more detail in a sec.

Note that in this XAML we haven’t defined either an instance of the ThemeShadow and we haven’t set a background on the items in the list. These will both be defined as part of the ItemContainerStyle, so that we can take advantage of the visual states to adjust the elevation (i.e. the Z axis translate), and thus the shadow cast.

Ok, so here’s the ItemContainerStyle.

<Style x:Key="ThousandItemContainerStyle"
        TargetType="ListViewItem">
    <Setter Property="Padding"
            Value="6" />
    <Setter Property="HorizontalContentAlignment"
            Value="Stretch" />
    <Setter Property="VerticalContentAlignment"
            Value="Stretch" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListViewItem">
                <Grid x:Name="Root">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Pressed">
                                <VisualState.Setters>
                                    <Setter Target="ContentBackground.Elevation"
                                            Value="0" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="PressedSelected">
                                <VisualState.Setters>
                                    <Setter Target="ContentBackground.Elevation"
                                            Value="0" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <local:ItemBackground Elevation="32"
                                            x:Name="ContentBackground"
                                            ContentPadding="{TemplateBinding Padding}" />
                    <ContentPresenter Margin="{TemplateBinding Padding}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Things to note:

  • I’ve removed a significant portion of the default ItemsContainerStyle you would normally get when you clone the built-in style. What I’m left with is a basic ContentPresenter element and a new UserControl of type ItemBackground.
  • ItemBackground has an initial Elevation of 32 but this property is adjusted when the user presses the button to control the elevation (see the Pressed and PressedSelected visual states), and thus the amount of shadow.

Of course, now we need to look at the ItemsBackground class so we can understand how it’s generating the shadow for each item. The XAML for the ItemBackground UserControl is similar to what we had in my previous post – a root Grid which defines the ThemeShadow resource and includes a Rectangle which is the background of the item. It’s the Rectangle that has the Shadow property set and will be generating the shadow that is to be cast.

<UserControl
    x:Class="ShadowTest.ItemBackground"
    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">
    <Grid Loaded="RootLoaded">
        <Grid.Resources>
            <ThemeShadow x:Name="SharedShadow" />
        </Grid.Resources>
        <Rectangle x:Name="Rectangle2"
                   Fill="Turquoise"
                   Margin="{x:Bind ContentPadding, Mode=OneWay}"
                   Shadow="{StaticResource SharedShadow}" />
    </Grid>
</UserControl>

We’re intercepting the Loaded event on the Grid in order to connect the ThemeShadow with the corresponding Grid to be the receiver for the shadow (in this case the Grid is the ParentBackgroundGrid defined on the page). Note that this logic could definitely be improved. It steps up the visual tree from the current item (i.e. the Grid in the ItemTemplate) all the way up to the Grid defined on the page called RootGrid.

private void RootLoaded(object sender, RoutedEventArgs e)
{
    var parent = VisualTreeHelper.GetParent(this);
    while (parent != null)
    {
        if(parent is Grid grid)
        {
            if(grid.Name == "RootGrid")
            {
                var bg = grid.Children.FirstOrDefault(x => x is Grid g && g.Name == "ParentBackgroundGrid");
                SharedShadow.Receivers.Add(bg);
                return;
            }
        }
        parent = VisualTreeHelper.GetParent(parent);

    }
}

Again, remembering that we can’t specify a ancestor as a Receiver for the shadow. Instead we’re going to find the first child element with the name ParentBackgroundGrid. This Grid will then be set as a Receiver for the ThemeShadow.

As we want to be able to adjust the Z axis translate when the user presses on the item in the list, we need to expose a mechanism whereby the ItemContainerStyle can simply set a property in the visual state definition. We need to define two dependency properties: ContentPadding, which determines the inset of content, and Elevation, which will determine the z translate value.

public Thickness ContentPadding
{
    get { return (Thickness)GetValue(ContentPaddingProperty); }
    set { SetValue(ContentPaddingProperty, value); }
}

public static readonly DependencyProperty ContentPaddingProperty =
    DependencyProperty.Register("ContentPadding", typeof(Thickness), typeof(ItemBackground), new PropertyMetadata(new Thickness(0)));

public int Elevation
{
    get { return (int)GetValue(ElevationProperty); }
    set { SetValue(ElevationProperty, value); }
}

public static readonly DependencyProperty ElevationProperty =
    DependencyProperty.Register("Elevation", typeof(int), typeof(ItemBackground), new PropertyMetadata(0, ElevationChanged));

private static void ElevationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    (d as ItemBackground).ChangeElevation((int)e.NewValue);
}

private void ChangeElevation(int newValue)
{
    Rectangle2.Translation = new Vector3(0, 0, newValue);
}

Let’s give this a run – the following image shows the list of items with the shadow appearing for each item. The only difference between the left and right sides is that in the list on the left side, the item “1000 items – 3” has been pressed.

What’s interesting about applying the shadow to the list of items is that the items almost appear to have rounded corners, in addition to the apparent elevation from the background.

The code in this blog post is very raw and I’ve no doubt there is a way to abstract the various steps to make it easier to define and connect the ThemeShadow with the shadow receiver.

Shadows in Windows (UWP) XAML Applications – Part 1 – ThemeShadow

I’ve been watching James Montemagno bring his AnimalCrossing app to life. The progress continues: #AnimalCrossing Turnip Tracking App powered by #Xamarin and amazing library creators. 99.9% shared code 🙂 pic.twitter.com/7CSlmk6nCJ — James Montemagno – 🔴 Live on Twitch (@JamesMontemagno) April 11, 2020 What’s super impressive is that this doesn’t look anything like a regular out … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 1 – ThemeShadow”

I’ve been watching James Montemagno bring his AnimalCrossing app to life.

What’s super impressive is that this doesn’t look anything like a regular out of the box Xamarin.Forms application. The use of custom fonts, curves, rounded corners and shadows gives this app its own unique look. Unfortunately, it would appear that some of the libraries used aren’t supported on Windows (UWP).

This got me thinking about the use of shadows in UWP, which lead to me sinking time into investigating what is and isn’t support. In this series of posts I’ll walk through some of the options.

Built-in Shadows

Before we get into adding our own shadows to elements in our application, it’s worth noting that UWP already comes with a lot of built-in shadow support. The guidance with regards to using shadows in UWP is that “shadows should be used in a purposeful rather than aesthetic manner”. As such, the built-in shadows appear on elements such as popups and tooltips but don’t appear gratuitously on every list item. For example, the following tooltip has a shadow that makes it appear elevated in relation to the rest of the content.

Dark Mode

I also wanted to see what support there was for dark theme. After switching my Windows theme to dark mode it was evident that there isn’t really any support for shadows. For example, here’s the same tooltip in dark mode.

Whilst there is still some illusion of elevation, this predominately comes from the border that’s been added to the tooltip, rather than an actual shadow. There is definitely no visible shadow around the sides and top of the tooltip, like there is in the light theme.

Now you might be saying “well duh, there is a shadow, you just can’t see it on the black background”. To which, my response is that a shadow, when it comes to applications, is much less about a shadow cast by a light hitting and object, and much more about the illusion of elevation and vertical separation of elements (as described by the Microsoft documentation on Z-depth). As such, even in dark mode you’d expect to see some shadow that illustrates a vertical separation of elements.

WinUI 3

Before we move on, I thought I’d take a quick look at whether there are any difference with regards to shadows in WinUI 3 (grab the WinUI 3 templates to get started). The following image shows the exact same button and tooltip combination, this time using the controls from WinUI 3.

Note that other than a change to the shape and border on the tooltip, there’s really no difference to the shadow cast by the tooltip. The behaviour in dark mode it basically the same too. Although I must admit, do like the new look tooltip with the slightly rounded corners and more gradient border, more than the standard UWP look.

Shadows using ThemeShadow

Whilst support for shadows has been around for quite a while for UWP applications, it was only recent that the ThemeShadow class was added (Windows 10, version 1903 – v10.0.18362.0), making it easier for developers to add shadow support to their applications. To demonstrate this in action, we’ll use a rather contrived example of two overlapping Rectangles, as defined in the following XAML.

<Grid>
    <Rectangle x:Name="Rectangle1"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Top"
                HorizontalAlignment="Right" />
    <Rectangle x:Name="Rectangle2"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Left" />
</Grid>

Running this example we can see that both Rectangle shapes appear as one connected shape, flat against the white background. We’ll use a ThemeShadow to elevate the Rectangle2 (bottom left) off the background, and provide some separation from Rectangle1 (top right).

We’ll start by creating a ThemeShadow instance as a Resource with the name SharedShadow. This will be set as the Shadow property on Rectangle2.

<Grid>
    <Grid.Resources>
        <ThemeShadow x:Name="SharedShadow" />
    </Grid.Resources>
    <Grid x:Name="BackgroundGrid" />
    <Rectangle x:Name="Rectangle1"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Top"
                HorizontalAlignment="Right" />
    <Rectangle x:Name="Rectangle2"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Left" 
                Shadow="{StaticResource SharedShadow}" />
</Grid>

Note that creating this resource and setting the Shadow property, creates the shadow but it won’t display until you tell the application what elements should show the shadow. This is done by adding elements to the Receivers collection on the ThemeShadow resource.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);
}

It’s important to note here that you can’t add an ancestor of the element casting the shadow to the Receivers collection. This seems rather counter-intuitive since you’d quite often want the shadow to appear on the background behind the element casting the shadow (which would typically be the parent grid or panel). However, it’s also easy to solve – in the above XAML a Grid called BackgroundGrid was added on which the shadow can be cast.

Even after doing all this, if you run this example you still won’t see a shadow. This is because all the elements are all technically at the same elevation (i.e. they have the same Z axis value of 0) so there is no shadow to be cast. To fix this, we simply need to translate Rectangle2 in the Z dimension.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);	
    Rectangle2.Translation += new Vector3(0, 0, 32);
}

Now we’re getting somewhere. The following image shows a shadow around Rectangle2. However, it still shows the two rectangles as being a single shape.

The reason there is no shadow being shown on Rectangle1 is that we haven’t told the application that it should show the shadow there. As before, the fix for this is to add Rectangle1 to the Receivers list of the ThemeShadow resource.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);	
    SharedShadow.Receivers.Add(Rectangle1);
    Rectangle2.Translation += new Vector3(0, 0, 32);
}

And there we have it – a shadow appearing on all sides of Rectangle2.

Of course, we couldn’t just leave it there as I’m sure you’re all wondering what happens in dark mode. Well, there’s good and bad news, as you can see in the following image.

The good news is that the shadow is still being created (see between the rectangles). The bad news is that the shadow hasn’t been inverted with the theme, meaning that you can’t see the shadow against the black background. This is seriously frustrating as it makes the ThemeShadow kind of pointless – it’s main premise was that it was suppose to handle everything for the developer.

Rounded Corners

Updated: I’d forgotten to add this last section on rounded corners in the initial post.

Going back to the designs that James had in the app he’s working on, the items all have rounded corners, giving it a softer, more casual look and feel. To do this in my simple example is relatively easy as I can just set the RadiusX and RadiusY properties.

<Rectangle x:Name="Rectangle2"
            Margin="50"
            Height="200"
            Width="200"
            Fill="Turquoise"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left"
            RadiusX="40"
            RadiusY="40"
            Shadow="{StaticResource SharedShadow}" />

But what happens to my shadow? Well, the answer is nothing good, as you can see from the following image

In addition to not being able to handle dark mode, it also appears that the ThemeShadow can’t handle non-square corners. According to the documentation, there’s also no support color. Overall the ThemeShadow is severely limited in the scenarios that it would be useful for.

Next up we’ll take a more in depth look at the Shadow property in UWP and how you can implement a shadow yourself.

Dual Screens with Surface Neo (Windows 10X) and Surface Duo (Android)

In October 2019 Microsoft announced a new category of dual screen device with flagship products Surface Neo and Surface Duo. Since then, there has been a massive undertaking by Microsoft to prepare developers of both Windows (UWP and Win32 apps) and Android apps for a new world of dual, or even multiple, screen devices. This … Continue reading “Dual Screens with Surface Neo (Windows 10X) and Surface Duo (Android)”

In October 2019 Microsoft announced a new category of dual screen device with flagship products Surface Neo and Surface Duo. Since then, there has been a massive undertaking by Microsoft to prepare developers of both Windows (UWP and Win32 apps) and Android apps for a new world of dual, or even multiple, screen devices. This included a day focused on building dual screen applications – Microsoft 365 Developer Day: Dual Screens (and here) (this event in itself was weird because it was branded as Microsoft 365 but other than a session on Microsof Graph it had very little to do with Microsoft 365 and everything to do with Windows and Android SDKs for dual screen devices). In this post I wanted to start exploring the dual screen emulators and talk about some points of interest when it comes to interacting with the screens.

Firstly, if you haven’t already, you can grab the emulator images either from the Microsoft Store (for the Windows 10X emulator) or via the Surface Duo SDK. Start at the documentation page for how to Get the Windows 10X development tools and how to Get the Surface Duo SDK.

Now that you’ve got the tools, let’s start to take a look at the emulators. Specifically I want to drill into how the different platforms handle spanning an application across the fold/hinge. The following image shows an image being spanned across the hinge on the Windows 10X emulator.

Notice how there’s a break in the image, making the image feel like it’s broken in two. What’s interesting is that when you take a screenshot using the emulator tools, this break isn’t visible, as shown in the following screenshot.

If we us the Maps application and again span the hinge, there is again a break across the hinge, making the map hard to process visually.

If we switch across to the Android emulator, you can now see that the image spans across the hinge without disturbing the image. The hinge masks or obscures part of the picture, but this is much easier for us to deal with.

And again, using the Android emulator you can see that the map continues beneath the hinge.

Ok, so let’s jump into taking a look at some code. In this case we’re going to look at some UWP code but rather than attempting to treat the two screens as a single page (as shown in soooooo many demos/samples) we’re going to use multiple windows for the application. Into a new UWP application I’ve added a Button and a TextBlock (just to show which window is which). The Button is used to launch new Windows, each showing the MainPage.

private async void Button_Click(object sender, RoutedEventArgs e)
{
    CoreApplicationView newView = CoreApplication.CreateNewView();
    int newViewId = 0;
    await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        Frame frame = new Frame();
        frame.Navigate(typeof(MainPage), null);
        Window.Current.Content = frame;
        // You have to activate the window in order to show it later.
        Window.Current.Activate();

        newViewId = ApplicationView.GetForCurrentView().Id;
    });
    bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}

When I first run this, I can see my application taking up a single screen.

Clicking the button the second Window appears on the other screen – this is exactly what you’d hope would happen.

If we now press the button on the second Window, another new Window appears, again on the alternative screen.

Now we can start to arrange the Windows. Here I’ve dragged Window 3 into the lower right corner of the second screen, below Window 2. (ignoring the mouse cursor, which was a result of me attempting to screen capture the drag experience) You can see that there is a visual cue as to what will happen when I release the drag operation.

Window 3 expands to take the lower half of the second screen and prompts me to select what I want to take the top half.

Clicking on Window 2 it expands to fill the top half of the screen.

What’s interesting about this example, which uses Multiple Windows instead of dual panes or a single Window that spans the hinge, is that you get complete control over how the individual windows are positioned. The default behaviour works nicely with Windows appearing in the correct screen, yet you can easily override it by dragging the windows to where you want them.

Along the way (probably because so many apps are modeled on the iOS notion of only doing one thing at a time) we as app developers have forgotten how to take advantage of multiple Windows. Now is the time to think about how best to extend your application to use multiple windows and prepare it for the next wave of multi-screen devices.