Windows 10, UWP and DeviceFamily-Specific Resource Dictionaries

A small thing for a short post – as you no doubt know by now, with Windows 10 and the Universal Windows Platform you can perhaps;

  • write a specific app for a device family (e.g. mobile, desktop, team, xbox, etc).
  • write apps that span device families, maybe even all of them.

for the latter, there’s probably going to come a point where you need to adapt either;

  • the UX to cope with device families having different input/output mechanisms – e.g. keyboards, mice, pen, touch, game controllers, gaze, gesture, speech, etc. and then big/small/multiple screens (or no screens) and so on.
  • the functionality to cope with device families running different variants of Windows built on a common core which then expose different ‘contracts’ through which are exposed different sets of APIs.

for the former, there are new ways (in XAML) to adjust the UI such that it responds nicely to different sizes of screen and so on through techniques like;

  • the new RelativePanel
  • new options in Visual State Management like automatically responding to size changes (via the AdaptiveTrigger) and driving changes by changing settings (including relative positioning of elements in a RelativePanel)

and XAML’s use of effective pixels helps a lot with its ‘abstraction’ of pixel size based on some combination of the device’s [resolution, physical size, viewing distance].

It’s perhaps better to respond to ‘screen size’ than it is to tie behaviour directly to device family because it’s getting harder to know what sort of hardware a device in a particular device-family might have.

This seems reasonably obvious for a device family like ‘desktop’ – we’re very used to ‘desktops’ suddenly growing a 2nd or 3rd monitor of different resolutions and pixel density or magically acquiring a mouse or keyboard or similar. The hardware is far from fixed.

For devices in other families like ‘mobile’ it feels like it might be ok to assume [‘mobile’ == small screen, one screen, touch input] but then that set of assumptions isn’t so likely to hold true in a world of Windows 10 with its ‘Continuum for Phones’ feature where, suddenly, such a device can have a large monitor and mouse attached to it.

For those scenarios where you’ve decided it is ‘ok’ to tie a particular piece of UI to a device-family, the Windows ‘Modern Resource Technology’ comes into play in that you can put ‘assets’ into your project under particular naming schemes that effectively means they are only applicable to a particular device family and the packaging and deployment bits do smart things to make sure that (only) the right bits turn up on the right devices.

I’ve illustrated that idea before in quite a few places (recently at the end of the video in this post and also in the video for this post) but I made a simple demo today that combined this with resource files that I thought I’d share here.

Here’s my ‘demo app’ running on desktop and mobile;

Capture

this is from a blank project where I’ve just set up a datacontext in my code behind;

using System;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

namespace App4
{
  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();

      this.Loaded += OnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
      Random r = new Random();

      this.DataContext =
        Enumerable.Range(1, 100).Select(
          i =>
            new SolidColorBrush(
              Windows.UI.Color.FromArgb(
                0xFF,
                (byte)r.Next(0, 255),
                (byte)r.Next(0, 255),
                (byte)r.Next(0, 255))));
    }
  }
}

so, it’s just a list of random colours and then I have a XAML page that binds to that in a GridView;

<Page
    x:Class='App4.MainPage'
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
    xmlns:local='using:App4'
    xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
    xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
    mc:Ignorable='d'>
    <Grid Background='{ThemeResource ApplicationPageBackgroundThemeBrush}'>
    <GridView ItemsSource='{Binding}' ItemTemplate='{StaticResource myTemplate}'>
    </GridView>
    </Grid>
</Page>

and then I have an App.xaml to bring in a resources dictionary;

<Application
    x:Class='App4.App'
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
    xmlns:local='using:App4'
    RequestedTheme='Light'>
  <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source='Resources.xaml'/>
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </Application.Resources>
</Application>

and, of course, the very small trick is to have 2 of these Resource files on a per-device-family basis;

Capture

with one containing a square based template and the other a circle based one;

<ResourceDictionary
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
    xmlns:local='using:App4.DeviceFamily_Desktop'>
  <DataTemplate x:Key='myTemplate'>
    <Rectangle Width='100' Height='100' Fill='{Binding}'/>
  </DataTemplate>
</ResourceDictionary>

and

<ResourceDictionary
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
    xmlns:local='using:App4.DeviceFamily_Mobile'>
  <DataTemplate x:Key='myTemplate'>
    <Ellipse Width='100' Height='100' Fill='{Binding}'/>
  </DataTemplate>
</ResourceDictionary>

and the job’s done.

I know, it’s only a small thing and it’s an ‘obvious’ thing but I thought it was a kind of ‘neat’ thing. Naturally, if I just wanted to just change something like the size per device-family (not sure I would) then I could just define some XAML resources for those sizes and pick them up as resource on a per device-family basis rather than change the whole template as I do here.

Update – thinking about this a little more, there’s another way in which I could do this which is via Visual States. That is, I could write just one XAML file;

<Page
    x:Class='App4.MainPage'
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
    xmlns:local='using:App4'
    xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
    xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
    mc:Ignorable='d'>
  <Page.Resources>
    <DataTemplate x:Key='square'>
      <Ellipse Width='100' Height='100' Fill='{Binding}'/>
    </DataTemplate>
    <DataTemplate x:Key='round'>
      <Rectangle Width='100' Height='100' Fill='{Binding}'/>
    </DataTemplate>
  </Page.Resources>
  <Grid Background='{ThemeResource ApplicationPageBackgroundThemeBrush}'>
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name='stateGroups'>
        <VisualState x:Name='Default'/>
        <VisualState x:Name='Mobile'>
          <VisualState.StateTriggers>
            <local:DeviceFamilyTrigger DeviceFamily='Windows.Team'/>
          </VisualState.StateTriggers>
          <VisualState.Setters>
            <Setter Target='gridview.ItemTemplate' Value='{StaticResource round}'/>
          </VisualState.Setters>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <GridView x:Name='gridview' ItemsSource='{Binding}'
              ItemTemplate='{StaticResource square}'>
    </GridView>
  </Grid>
</Page>

and that contains both XAML DataTemplates for the mobile device family and any other families and relies on a custom trigger to switch based on device family (there are a few of these on the web already);

namespace App4
{
  using Windows.System.Profile;
  using Windows.UI.Xaml;

    public class DeviceFamilyTrigger : StateTriggerBase
    {
        private string _deviceFamily;

        public string DeviceFamily
        {
            get
            {
                return _deviceFamily;
            }
            set
            {
                _deviceFamily = value;
                CheckValue();
            }
        }
        private void CheckValue()

        {
            SetActive(AnalyticsInfo.VersionInfo.DeviceFamily == DeviceFamily);

        }

        public DeviceFamilyTrigger()

        {
            CheckValue();
        }
    }
}

and that’s another way of achieving the same thing without having to partition into multiple resource files.