Xamarin Guide 7: Add Support For WinRT Apps

Scope

This Xamarin Workshop Guide was created for the The Portuguese National Meeting of IT Students (ENEI) by Sara Silva and the original content is available here. To extend it to the global community, it was published in a new project called Xam Community Workshop, and the main goal is for any developer, or user group to customize it to their events.

Before reading this article you must read:

Guide 7. Add support for WinRT Apps

In this step you will learn how to add support to WinRT apps. That means you will create a Universal app that will use Xamarin Forms for Windows (Preview).

To start, create a new project based on an Universal App template, as described in Figure 1 and Figure 2.



Figure 1: Create new project



Figure 2: Blank App (Universal App)

The result will be as in Figure 3.

Figure 3: The solution



Now, you will add the ENEI.SessionsApp reference to the WinRT project, as described in Figure 4 and Figure 5.



Figure 4: Add Reference



Figure 5: Selecting the ENEI.SessionApp

In the Windows Phone 8.1 (WinRT) app, you will get an error as in the following:



Figure 6: Error adding ENEI.SessionsApp

This error means that the ENEI.SessionsApp is a Portable Class Library (PCL) that does not have support for Windows Phone 8.1 (WinRT). This way, you need to change the PCL project (in Properteis) to support this target, as described in Figure 6:



Figure 7: Add support to Windows Phone 8.1 (WinRT)

After that, you will have the references added to each project, as in the following:



Figure 8: The references

Before you create code, you need to install the Xamarin Forms Windows (Preview) NuGet Package as in the following:



Figure 9: Opening the “Manage NuGet Packages…”



Figure 10: Installing the Xamarin Forms Windows (Preview)



Figure 11: The Xamarin Forms Windows (Preview) installed

Let's do some code!

In general, you should not do so many changes to have the WinRT apps. First, you need to start by doing the Xamarin Forms setup, this way you need change the OnLaunched method in the App.xaml.cs that has the following:

  1. protected override void OnLaunched(LaunchActivatedEventArgs e)  
  2. {  
  3.     #if DEBUG  
  4.     if (System.Diagnostics.Debugger.IsAttached)  
  5.     {  
  6.         this.DebugSettings.EnableFrameRateCounter = true;  
  7.     }  
  8.     #endif  
  9.     Frame rootFrame = Window.Current.Content as Frame;  
  10.   
  11.     // Do not repeat app initialization when the Window already has content,  
  12.     // just ensure that the window is active  
  13.     if (rootFrame == null)   
  14.     {  
  15.         // Create a Frame to act as the navigation context and navigate to the first page  
  16.         rootFrame = new Frame();  
  17.   
  18.         // TODO: change this value to a cache size that is appropriate for your application  
  19.         rootFrame.CacheSize = 1;  
  20.   
  21.   
  22.         global: Xamarin.Forms.Forms.Init(e);  
  23.     }  
  24. }  
As in the other platform, the Init method will initialize the Xamarin Forms, in this case it is needed for sending the LaunchActivatedEventArgs.

And we need to change the MainPage contructor, from each WinRT app, to:

  1. public MainPage()  
  2. {  
  3.     this.InitializeComponent();  
  4.   
  5.     this.NavigationCacheMode = NavigationCacheMode.Required;  
  6.     LoadApplication(new ENEI.SessionsApp.App());  
  7. }  
And the page type must be changed to: 
  • In Windows Phone 8.1 (WinRT):
    1. <forms:WindowsPage    
    2.     x:Class="ENEI.SessionsApp.WinRT.MainPage"    
    3.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
    4.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
    5.     xmlns:forms="using:Xamarin.Forms.Platform.WinRT"    
    6.     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    
    7.     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
    8.     mc:Ignorable="d"    
    9.     Foreground="Black"    
    10.     Background="White">   
  • In Windows 8.1 Store App (WinRT):
    1. <forms:WindowsPage  
    2.     x:Class="ENEI.SessionsApp.WinRT.MainPage"  
    3.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
    4.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
    5.     xmlns:forms="using:Xamarin.Forms.Platform.WinRT"  
    6.     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
    7.     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
    8.     mc:Ignorable="d"  
    9.     Foreground="Black"  
    10.     Background="White">  

Now, we must define the background color from the NavigationPage to white that should be defined from the beginner:

  1. MainPage = new NavigationPage(new SessionsView())  
  2. {    
  3.     BarBackgroundColor = Color.White,  
  4.     BarTextColor = Color.Black,  
  5.     BackgroundColor = Color.White,  
  6. };  
At this moment, you will see the header in the right way but the images from the menu are not displayed, because each image was defined by:
  1. <Image Grid.Column="7">  
  2.     <Image.WidthRequest>  
  3.         <OnPlatform Android="30" WinPhone="48" iOS="30" x:TypeArguments="x:Double" />  
  4.     </Image.WidthRequest>  
  5.     <Image.HeightRequest>  
  6.         <OnPlatform Android="30" WinPhone="48" iOS="30" x:TypeArguments="x:Double" />  
  7.     </Image.HeightRequest>  
  8.     <Image.Source>  
  9.         <OnPlatform x:TypeArguments="ImageSource">  
  10.             <OnPlatform.iOS>  
  11.                 <FileImageSource File="ic_action_list.png" />  
  12.             </OnPlatform.iOS>  
  13.             <OnPlatform.Android>  
  14.                 <FileImageSource File="ic_action_list.png" />  
  15.             </OnPlatform.Android>  
  16.             <OnPlatform.WinPhone>  
  17.                 <FileImageSource File="Images/ic_action_list.png" />  
  18.             </OnPlatform.WinPhone>  
  19.         </OnPlatform>  
  20.     </Image.Source>  
  21.     <Image.GestureRecognizers>  
  22.         <TapGestureRecognizer x:Name="DetailsGesture" CommandParameter="{Binding}" Tapped="DetailsGesture_OnTapped" />  
  23.     </Image.GestureRecognizers>  
  24. </Image>  
They using OnPlatform to set the image's source and the image's height and width. In the preview version used, it is not possible to use that but in the future it will work for Windows apps as it works for the other platforms.

This way, you need to create a workaround to ensure the images will be loaded and it will use converters, as in the following:

  1. <ContentPage.Resources>  
  2.     <ResourceDictionary>  
  3.       <converters:FavoriteImageConverter x:Key="FavoriteImageConverter" />  
  4.       <converters:ImageSizeConverter x:Key="ImageSizeConverter"/>  
  5.       <converters:ImageUrlConverter x:Key="ImageUrlConverter"/>  
  6.       <converters:RowHeightConverter x:Key="RowHeightConverter"/>  
  7.     </ResourceDictionary>  
  8. </ContentPage.Resources>  
And:
  1. <Image Grid.Column="7"Source="{Binding Converter={StaticResource ImageUrlConverter}, ConverterParameter=Details}" HeightRequest="{Binding Converter={StaticResource ImageSizeConverter}}" WidthRequest="{Binding Converter={StaticResource ImageSizeConverter}}">  
  2.     <Image.GestureRecognizers>  
  3.         <TapGestureRecognizer x:Name="DetailsGesture" CommandParameter="{Binding}"Tapped="DetailsGesture_OnTapped" />  
  4.     </Image.GestureRecognizers>  
  5. </Image>  
Where the converters are defined as in the following.

In ImageSizeConverter, to define the Height and the Width for the images:

  1. public class ImageSizeConverter:IValueConverter  
  2. {  
  3.     public object Convert(object value, Type targetType, object parameter, CultureInfo culture)  
  4.     {  
  5.         if (value != null)  
  6.         {  
  7.   
  8.             if (Device.OS == TargetPlatform.Android || Device.OS == TargetPlatform.Windows)  
  9.             {  
  10.                 return 48;  
  11.             }  
  12.             if (Device.OS == TargetPlatform.iOS)  
  13.             {  
  14.                 return 30;  
  15.             }  
  16.         }  
  17.         return value;  
  18.     }  
  19.   
  20.     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)  
  21.     {  
  22.         throw new NotImplementedException();  
  23.     }  
  24. }     
In ImageUrlConverter, to define the path for the images:
  1. public class ImageUrlConverter:IValueConverter  
  2. {  
  3.     public object Convert(object value, Type targetType, object parameter, CultureInfo culture)  
  4.     {  
  5.         if (parameter !=null && !string.IsNullOrEmpty(parameter.ToString()))  
  6.         {  
  7.             var imageUrl = string.Empty;  
  8.             switch (parameter.ToString())  
  9.             {  
  10.                 case "Like":  
  11.                     imageUrl= Device.OS == TargetPlatform.WinPhone || Device.OS == TargetPlatform.Windows ?  
  12.                     "Images/ic_action_like.png":  
  13.                     "ic_action_like.png";  
  14.                     break;  
  15.                 case "Share":  
  16.                     imageUrl = Device.OS == TargetPlatform.WinPhone || Device.OS == TargetPlatform.Windows ?  
  17.                    "Images/ic_action_share_2.png" :  
  18.                    "ic_action_share_2.png";  
  19.                     break;  
  20.                 case "Details":  
  21.                     imageUrl = Device.OS == TargetPlatform.WinPhone || Device.OS == TargetPlatform.Windows ?  
  22.                    "Images/ic_action_list.png" :  
  23.                    "ic_action_list.png";  
  24.                     break;  
  25.             }  
  26.   
  27.             return ImageSource.FromFile(imageUrl);  
  28.         }  
  29.         return null;  
  30.     }  
  31.   
  32.     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)  
  33.     {  
  34.         throw new NotImplementedException();  
  35.     }  
  36. }  
In RowHeightConverter to define the size of the row from the ListView:
  1. public class RowHeightConverter:IValueConverter  
  2. {  
  3.     public object Convert(object value, Type targetType, object parameter, CultureInfo culture)  
  4.     {  
  5.         if (value != null)  
  6.         {  
  7.   
  8.             if (Device.OS == TargetPlatform.Android || Device.OS == TargetPlatform.iOS)  
  9.             {  
  10.                 return 150;  
  11.             }  
  12.             if (Device.OS == TargetPlatform.WinPhone)  
  13.             {  
  14.                 return 180;  
  15.             }  
  16.             if (Device.OS == TargetPlatform.Windows)  
  17.             {  
  18.                 return 200;  
  19.             }  
  20.         }  
  21.         return value;  
  22.     }  
  23.   
  24.     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)  
  25.     {  
  26.         throw new NotImplementedException();  
  27.     }  
  28. }  
At this moment if you run the applications you will have something as in the following:


Figure 12: The WinRT apps