Xamarin.Forms and panorama in a Windows Phone app

One common scenario when building an app is that we want to use tabs on iOS and Android and a Panorama on Windows Phone. Xamarin.Forms has a control called TabbedPage that render tabs on iOS and Android. At the bottom of the page for iOS and at the top of the page under the ActionBar for Android, for Windows Phone a Pivot is rendered. To render a Panorama instead is a quite simple with conditional compile and a custom render.

Xamarin.Forms has a control that renders a Panorama on Windows Phone that is called CarouselPage. But we don’t want to use it for iOS and Android. The solution for that is to use conditional compile.

In the tab host page you want to inherit from CarouselPage instead of TabbedPage. To use conditional compile for the inheritance of the class we can’t have a XAML file for the page. The example below inherits from CustomCarouselPage, it is only an empty class that inherits from CarouselPage, the only reason is that we want to create a custom renderer for the CarouselPage.

#if WINDOWS_PHONE
    public partial class MainView : CustomCarouselPage
    {
#else
    public partial class MainView : ExtendedTabbedPage
    {
#endif

To get it work with conditional compile we also need to add “WINDOWS_PHONE” to conditional compile symbols under the Build tab on project properties for the shared project. I recommend to create a two new build configurations for the solution, I use to call them Debug_WP (copy settings from Debug) and Release_WP(copy settings for Release).

The CarouselPage doesn’t have titles for the panorama items. To fix that we need to create a custom renderer in the Windows Phone project. While we want to extend the default renderer we can inherit from CarouselPageRenderer. If we want to style the PanoramaHeader and the PanoramaItemHeader we can do it by creating templates and then add them to the Panorama as shown below.

[assembly: ExportRenderer(typeof(CustomCarouselPage), typeof(CustomCarouselPageRenderer))]
namespace MyApp.WinPhone.Renderers
{   
    public class CustomCarouselPageRenderer : CarouselPageRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);
 
            var titleTemplate = App.Current.Resources.SingleOrDefault(x => x.Key.ToString() == "PanoramaTitle").Value as System.Windows.DataTemplate;
            if (titleTemplate != null)
            {
                TitleTemplate = titleTemplate;
            }
 
            var headerTemplate = App.Current.Resources.SingleOrDefault(x => x.Key.ToString() == "PanoramaItemHeader").Value as System.Windows.DataTemplate;
            if (headerTemplate != null)
            {
                HeaderTemplate = headerTemplate;
            }
 
            for (int i = 0; i < Items.Count; i++)
            {
                var item = Items[i] as PanoramaItem;
                var page = Element as CarouselPage;
                item.Header = page.Children[i].Title;
 
                var content = item.Content as CarouselPagePresenter;
                content.Margin = new System.Windows.Thickness(0, -30, 0, 0);
            }
        }
    }
}

Here is an example how the templates in App.xaml (in the Windows Phone project) can look like:

 <Application.Resources>
        <DataTemplate x:Key="PanoramaTitle">
            <ContentPresenter>
                <TextBlock Text="{Binding}" FontSize="100" Foreground="#FFFFFF" Margin="0,40,0,0" />
            </ContentPresenter>
        </DataTemplate>
        <DataTemplate x:Key="PanoramaItemHeader">
            <Grid>
                <ContentPresenter>                  
                    <TextBlock Text="{Binding}" FontSize="30" Foreground="#FFFFFF" Margin="-10,20,0,0"  />
                </ContentPresenter>
            </Grid>
        </DataTemplate>
    </Application.Resources>

One thought on “Xamarin.Forms and panorama in a Windows Phone app

Leave a Reply

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