IdentityMine

| Tags: Laurent Bugnion, Windows 10

One of the most exciting features of the new Windows 10 mobile devices is Continuum. It means that you can connect your Windows 10 phone to an external monitor, keyboard and mouse, and use it as a lightweight computer. Since it uses standard, non-proprietary protocols, you have a number of options to connect:

wp_ss_20160102_0001 (2)

 

 

 

 

 

  • You can use a pure wireless solution with Miracast for the display and Bluetooth for mouse/keyboard. Miracast is built into modern displays and TVs these days, or you can use Miracast-enabled sticks like the Microsoft wireless display adapter.
  • Or you can choose wires and use the Display Dock, and a wired mouse / keyboard.
  • Or of course you can use a combination of the above. These technologies are standard and you can choose!

I shot a small video showing how the Continuum works with a Microsoft Lumia 950XL, a Microsoft Wireless Display Adapter, a Bluetooth mouse and keyboard.

For developers:

Contrarily to what some tech journalists are stating, Continuum works with any Windows 10 Universal Windows Platform (UWP) application. It is NOT reserved to Microsoft-only apps (though it works of course great with Word, Excel, PowerPoint, Groove music, video and more). But, any UWP will work on Continuum.

There is one step that developers need be aware of: adapting the UI to the larger screen. We will now walk through what options we have for this and how to set up your XAML for optimal Continuum support. Access a complete working sample here.

Using the DeviceFamily or using the screen size?

When I first heard about the DeviceFamily feature, I thought it was a great idea. This allows to split specific pages according to the devices that they are running on. To be clear, this is still one app, one binary but you just adapt the UI depending on the device family. So in effect, you can define one MainPage.xaml for mobile devices (DeviceFamily-Mobile), another MainPage.xaml for desktop devices (DeviceFamily-Desktop), and more such as Xbox, SurfaceHub, IOT, etc. I wrote about adaptive UI before and mentioned DeviceFamily, and there is also a useful reference on MSDN.

With Continuum, however, there is a catch! Even though your phone acts as a computer and shows the UI on a large monitor, it is still the same device. So, the DeviceFamily does not change. I made a sample demonstrating this. It’s a standard UWP with three secondary pages:

  • DeviceFamilyPage: This uses the DeviceFamily technique, having split the page in three different XAML pages: One in the DeviceFamily-Desktop folder (for desktop class devices), one in the DeviceFamily-Mobile folder (for mobile devices) and one fallback one in the root.
  • VsmAndTriggerPage: This uses a helper class deriving from StateTriggerBase to determine the DeviceFamil, and the VisualStateManager to create different states for different devices. This has all the code in one XAML page, but it has the same issue than above: The DeviceFamily doesn’t change on Continuum.
  • VsmPage: This uses a different helper class called AdaptiveTrigger to change the UI based on the screen size, but not based on the device family.

2016-01-03_15-36-05

DeviceFamily

If you open the three different DeviceFamilyPage.xaml instances, you will see that one has a TextBlock showing “Mobile device family page,” one shows “Desktop device family page” and the fallback one shows “Fallback device family page.” If we run the app on a Surface Pro (or any desktop class device) and press the button labeled “See the DeviceFamily page,” you will see the following image:

2015-12-31_15-41-15

If you run the same app on a phone though, you will see the following image:

wp_ss_20151231_0001

If we connect the phone to an external monitor though Continuum however, we continue to see the text “Mobile device family page.” When you think about it, it makes sense! It’s still the same device family, just with a different display. But of course it means that we will need a different strategy to adapt our UI on Continuum.

DeviceFamily with a trigger

This strategy is shown in the page VsmAndTriggerPage. If you open it, you will see that I am using the VisualStateManager with a custom trigger.

<Grid>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="Default" />

            <VisualState x:Name="Mobile">
                <VisualState.StateTriggers>
                    <helpers:DeviceFamilyTrigger
                     DeviceFamily="Windows.Mobile" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="DisplayText.Text"
                            Value="Mobile family" />
                </VisualState.Setters>
            </VisualState>

            <VisualState x:Name="Desktop">
                <VisualState.StateTriggers>
                    <helpers:DeviceFamilyTrigger
                     DeviceFamily="Windows.Desktop" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="DisplayText.Text"
                            Value="Desktop family" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <TextBlock x:Name="DisplayText"
                Text="Default value "
                Style="{StaticResource DisplayTextStyle}" />
</Grid>

Note that in XAML, I am using the DeviceFamily names “Windows.Mobile” and “Windows.Desktop.” This corresponds to the value returned by the AnalyticsInfo.VersionInfo.DeviceFamily. This is what I used in the DeviceFamilyTrigger helper class as shown below. This class is using its SetActive base method to trigger the corresponding state in the VisualStateManager.

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();
    }
}

However, as previously discussed, AnalyticsInfo.VersionInfo.DeviceFamily returns “Windows.Mobile” even when the device runs in Continuum mode, so here too, while this is a convenient way to detect the device family and adapt the UI, this cannot be used to make your app Continuum-able. :)

VisualStateManager and AdaptiveTrigger

This last method doesn’t work based on DeviceFamily but instead uses the built-in AdaptiveTrigger to check the screen size. This is implemented in the VsmPage.xaml as shown below (and as explained in my previous article about adaptive UI).

<Grid>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="Default" />

            <VisualState x:Name="Mobile">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="0" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="DisplayText.Text"
                            Value="Small screen" />
                </VisualState.Setters>
            </VisualState>

            <VisualState x:Name="Desktop">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="800" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="DisplayText.Text"
                            Value="Large screen" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <TextBlock x:Name="DisplayText"
                Text="Default value "
                Style="{StaticResource DisplayTextStyle}" />
</Grid>

As expected, this works just fine when the application runs in the Continuum: On the mobile device, we see “Small screen” and on the large device, we see “Large screen.” And so, if you want your application to work fine in Continuum, this is the way you should choose.

Note that one small disadvantage of this method is that there is only one XAML page, and so this can lead to “spaghetti code” where the VSM is used to show/hide/move the UI elements depending on its state. This can be mitigated however, by storing some of this XAML in a UserControl, or even in a DataTemplate stored in external resource dictionaries used on a ContentControl as shown below. Here, based on the state, we assign a different DataTemplate to the ContentControl in the page. These DataTemplate (Narrow or Large ones) can be saved in external ResourceDictionaries. Using this technique, it is relatively easy to split the page in meaningful parts for desktop, mobile and other scenarios.

<Page.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="DisplayTextTemplate-Large">
            <StackPanel>
                <TextBlock Text="{Binding}"
                            Style="{StaticResource DisplayTextStyle}" />
                <TextBlock Text="This is the template for large screens"
                            Style="{StaticResource DisplayTextStyle}" />
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="DisplayTextTemplate-Narrow">
            <StackPanel>
                <TextBlock Text="{Binding}"
                            Style="{StaticResource DisplayTextStyle}" />
                <TextBlock Text="This is the template for narrow screens"
                            Style="{StaticResource DisplayTextStyle}" />
            </StackPanel>
        </DataTemplate>
    </ResourceDictionary>
</Page.Resources>

<Grid>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="Default" />

            <VisualState x:Name="Mobile">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="0" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="MyContentControl.ContentTemplate"
                            Value="{StaticResource DisplayTextTemplate-Narrow}" />
                </VisualState.Setters>
            </VisualState>

            <VisualState x:Name="Desktop">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="800" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="MyContentControl.ContentTemplate"
                            Value="{StaticResource DisplayTextTemplate-Large}" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <ContentControl DataContext="Hello world"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    x:Name="MyContentControl" />

</Grid>

Conclusion

In conclusion, we see that if you want to adapt your Windows 10 universal application to Continuum, you want to consider the screen size as a trigger, as opposed to the DeviceFamily. Now that’s not saying that DeviceFamily is useless, but I think that the Continuum is a fantastic feature which can be available to your mobile app for free if you already support a desktop environment anyway (which would be kind of crazy to ignore because of the huge market that it offers). I hope that this article and the attached sample help you to understand how this all works, and take advantage of this fantastic new feature in Windows 10.

Happy coding!
Laurent

Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedInPin on PinterestShare on RedditShare on TumblrEmail this to someoneDigg thisFlattr the authorShare on StumbleUpon

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>