vendredi 25 mars 2016

Using adaptive triggers to build an application that can run on multiple Windows 10 platforms (Mobile/Desktop) XAML

Building applications is hard, and making them compatible on multiple platforms is a headache. You can either create multiple apps for specific platforms and use TargetDeviceFamily like in this tutorial or use AdaptiveTriggers so that you can adapt your view to the screen of a device.
On the Dailymotion Windows 10 application we wanted to use the same views on Mobile and Desktop devices. This is where adaptive triggers come to the rescue.
In this tutorial I will explain how to use AdaptiveTriggers to resize the width of our ItemTemplate inside a Gridview, when the application changes size. When the application is reduced to a certain size, I want the ItemTemplate that has a fixed size (300 pixels) to take up the whole width of the page.
Because a Gif is better then a 1000 words, this is the effect that I would like:
Adaptive cats

This tutorial will cover:

  1. What are AdaptiveTriggers
  2. How to use AdaptiveTriggers on a DataTemplate

What are Adaptive Triggers

Quick description: adaptive triggers allow you to create a rule (or set of rules) that can trigger a change in the visual state of your application (you can change the Size/Color/Visibility/etc of an element for example). This can allow you to resize elements without having to listen to the Window.SizeChanged event (that's AWESOME! let's do this!).
A lot more information can be found on MSDN here

How to use adaptive triggers on Items inside a GridView

The trick to using adaptive triggers inside a GridView is to implement it inside theDataTemplate, however, you can't create a
 <VisualStateManager.VisualStateGroups />
inside a resource file, so how is this possible?
You need to create a UserControl that you reference in your DataTemplate like this:
 <DataTemplate>
     <controls:AdaptiveCatItem />
 </DataTemplate>
And you set your VisualStateManager in the UserControl.
Here is an example on my User Control:
<UserControl x:Class="AdaptiveTriggers.Win10.Controls.AdaptiveCatItem"             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">  
    <Grid x:Name="TemplateRoot"
          Height="250"
          Width="300"
          HorizontalAlignment="Stretch"
          Margin="2"
          VerticalAlignment="Top">        
          <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="VisualStateGroup">
                <VisualState x:Name="VisualStateNarrow">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger x:Name="VisualStateNarrowTrigger" MinWindowWidth="0" />                              
          </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="TemplateRoot.Width" Value="Auto" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualStateNormal">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger x:Name="VisualStateNormalTrigger" MinWindowWidth="521" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="TemplateRoot.Width" Value="300" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <Grid.RowDefinitions>
            <RowDefinition Height="190" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>   

        <Image Grid.Row="0"
               HorizontalAlignment="Center"
               Source="{Binding ThumbnailUri}"
               Stretch="UniformToFill" />

        <Grid Grid.Row="1"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Top"
              Background="Transparent">
            <TextBlock Grid.Row="0"
                       Margin="5,5,5,5"
                       Text="{Binding Title,
                       FallbackValue='This is the cat image of the year'}"
                       />
        </Grid>
    </Grid>    
</UserControl>  
My VisualStateManager has two important AdaptiveTriggers. The first one is named VisualStateNarrowTrigger with a MinWindowWidth of 0 and another one named VisualStateNormalTrigger with a MinWindowWidth of 521. In this example, VisualStateNormalTrigger is the most important one.
What VisualStateNormalTrigger does is when the application is larger than 521 pixels then my items in the GridView have a width of 300. When my application width is smaller than 521 pixels the trigger VisualStateNarrowTrigger is fired and the width of my items in the GridView are set to Auto (which means that it will take up as much space as it can).
Going back to my VisualStateManager, here are the two important parts:
  • As mentioned above, VisualState allows me to set the width to auto on my grid element named TemplateRoot, when the application has a width of 0 pixels or more:
 <VisualState.StateTriggers>
     <AdaptiveTrigger x:Name="VisualStateNarrowTrigger" MinWindowWidth="0" />
 </VisualState.StateTriggers>
 <VisualState.Setters>
    <Setter Target="TemplateRoot.Width" Value="Auto" />
 </VisualState.Setters>
  • This VisualState allows me to set the Width to 300 on my grid element named TemplateRoot when the width of my application is 521 pixel or more:
   <VisualState.StateTriggers>
      <AdaptiveTrigger x:Name="VisualStateNormalTrigger" MinWindowWidth="521" />
   </VisualState.StateTriggers>
   <VisualState.Setters>
       <Setter Target="TemplateRoot.Width" Value="300" />
   </VisualState.Setters>
And there you have it, you can now resize the items that are inside your GridView when your application is resized without using any C# codeAdaptive cats
All of the code can be found on Github here
This piece of XAML code is what has allowed our Dailymotion application to be both Mobile and Tablet compatible without havving to code two appsDownload our sample here and give it a try!
Happy Coding =)

PS: as you can guess I like cats ^^

vendredi 4 mars 2016

Using an adaptive trigger to detect what platform your application is running on (Windows 10, XAML, C#)

I recently had issues with not knowing what platform my application was running on and wanted to have a specific UI depending on the platform and not depending on the size of the window.

Previously I had created a simple class (here) that allows you very simply detect what platform you are running on.  Next all I needed was to create a custom state trigger based on my previous class and we will be able to detect whether we are running on a Mobile, Tablet or Xbox platform.

First we need to create class that inherits from StateTriggerBase as follows:

namespace Win10.StateTriggers
{
    //you will also need https://gist.github.com/Delaire/37cbe07738df34bfd5e8
    public class DeviceFamilyStateTrigger : StateTriggerBase
    {
        private static DeviceTypeEnum deviceFamily;

        static DeviceFamilyStateTrigger()
        {
            deviceFamily = DeviceTypeHelper.GetDeviceType();
        }

        public DeviceTypeEnum DeviceFamily
        {
            get { return (DeviceTypeEnum)GetValue(DeviceFamilyProperty); }
            set { SetValue(DeviceFamilyProperty, value); }
        }

        public static readonly DependencyProperty DeviceFamilyProperty =
            DependencyProperty.Register("DeviceFamily", typeof(DeviceTypeEnum), typeof(DeviceFamilyStateTrigger),
            new PropertyMetadata(DeviceTypeEnum.Other, OnDeviceTypePropertyChanged));

        private static void OnDeviceTypePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (DeviceFamilyStateTrigger)d;
            var val = (DeviceTypeEnum)e.NewValue;

            if (deviceFamily == val)
                obj.IsActive = true;
        }

        private bool m_IsActive;

        public bool IsActive
        {
            get { return m_IsActive; }
            private set
            {
                if (m_IsActive != value)
                {
                    m_IsActive = value;
                    base.SetActive(value);
                    if (IsActiveChanged != null)
                        IsActiveChanged(this, EventArgs.Empty);
                }
            }
        }

        public event EventHandler IsActiveChanged;
    }   
}

what we have here is that we get the current platform type and check to see if our DeviceFamily property is of the same value, if so then the trigger will return an IsActive to true.

Here is how to use it in your XAML code: (the data in here is fake of course)

<VisualStateManager.VisualStateGroups>
   <VisualStateGroup x:Name="AbcVisualStateGroup">
      <VisualState x:Name="AbcVisualStateNarrow">
          <VisualState.StateTriggers>
     <AdaptiveTrigger x:Name="AbcVisualStateNarrowTrigger" MinWindowWidth="100" />
   </VisualState.StateTriggers>
   <VisualState.Setters>
     <Setter Target="AbcControl.(UIElement.Visibility)" Value="Collapsed" />
   </VisualState.Setters>
     </VisualState>
  <VisualState x:Name="AbcVisualStateNormal">
     <VisualState.StateTriggers>
       <AdaptiveTrigger x:Name="AbcVisualStateNormalTrigger" MinWindowWidth="400" />
       <stateTriggers:DeviceFamilyStateTrigger DeviceFamily="Xbox" />
         </VisualState.StateTriggers>
  <VisualState.Setters>
    <Setter Target="AbcControl.(UIElement.Visibility)" Value="Visible" />
 </VisualState.Setters>
      </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

What we can see here is that the state AbcVisualStateNormal will be called when we have MinWindowWidth="400"  or when DeviceFamily="Xbox". 

As you can see it was quite simple =)

Here are my 2 classes that you will need DeviceTypeHelper.cs and DeviceFamilyStateTrigger.cs


Happy coding!