Panorama Tricks – Using XAML for your Background


If you are doing Windows Phone 7 development, chances are that you are either using or have considered using the Panorama. To make a truly glorious Panorama application, to immerse the user in the experience, or for branding purposes, many applications pay a lot of attention to the background. The Panorama has a Background property of type Brush. Although this is most often used with an ImageBrush, it can also be a SolidColorBrush or a BradientBrush. Using gradients is not recommended, unless you are a fan of color banding. It would be nice if the ImageBrush could be used with a WriteableBitmap, but that doesn’t work. But there is something better…

Unlocking Panorama.Background to allow arbitrary content

If you examine Panorama.UpdateBackground using Reflector, you can see that the background ContentPresenter’s Height is always set to the viewport’s height. It handles a Background that is set to a SolidColorBrush by setting width of the background’s ContentPresenter to the viewport width; if it is a GradientBrush, it sets the width to the width of the items; for ImageBrush it does some magic to wait for the image to be loaded, and then sets the background’s width to the pixel width of the image. It also sets a property ominously called “IsStatic”. Keen observers will also be able to see why WriteableBitmaps can’t be used as the ImageSource of your ImageBrush. But you don’t really want that anyway, because the perf is not good, and anything you can do with a WriteableBitmap, by definition you can do with XAML.

If you want to be able to put arbitrary XAML in the background, you need to somehow do two things:

  1. set the width of the background’s ContentPresenter
  2. set IsStatic to false

You can get this done in code, but it is a bit of a pain, and frankly, a little sketchy. Fortunately, there’s an easier way to do this. Remember the good old days of getting stuff to go where you want it in HTML by using 1 x 1 pixel transparent GIFs? Well, I don’t propose doing anything as old-school-hacky as that. No, this method uses a modern 1 x N transparent PNG (where N is the desired width of your background.) OK, I guess this is a little on the sketchy side, too, but like I said, it has the virtue of being easy.

The attached solution includes “dummy.png”, an 800 x 1 transparent PNG that I created using Paint.NET. It is included in the project, and the build action is set to “Resource”. In the main page’s XAML, the Panorama.Background is set to an ImageBrush that has dummy.png as its ImageSource. The other thing that we have to do is to re-template the Panorama, to include our new spiffy XAML background. Here’s what the page looks like:

Code Snippet
  1. <phone:PhoneApplicationPage
  2.     x:Class=”PanoramaXamlBackground.MainPage”
  3.     xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
  4.     xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
  5.     xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
  6.     xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
  7.     xmlns:d=”http://schemas.microsoft.com/expression/blend/2008″
  8.     xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006″
  9.     xmlns:controls=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls”
  10.     xmlns:controlsPrimitives=”clr-namespace:Microsoft.Phone.Controls.Primitives;assembly=Microsoft.Phone.Controls”
  11.     mc:Ignorable=”d” d:DesignWidth=”480″ d:DesignHeight=”800″
  12.     FontFamily=”{StaticResource PhoneFontFamilyNormal}
  13.     FontSize=”{StaticResource PhoneFontSizeNormal}
  14.     Foreground=”{StaticResource PhoneForegroundBrush}
  15.     SupportedOrientations=”Portrait” Orientation=”Portrait”
  16.     shell:SystemTray.IsVisible=”False”>
  17.  
  18.     <phone:PhoneApplicationPage.Resources>
  19.         <ControlTemplate x:Key=”panoramaTemplate” TargetType=”controls:Panorama”>
  20.             <Grid>
  21.                 
  22.                 <Grid.RowDefinitions>
  23.                     <RowDefinition Height=”auto”/>
  24.                     <RowDefinition Height=”*”/>
  25.                 </Grid.RowDefinitions>
  26.  
  27.                 <controlsPrimitives:PanningBackgroundLayer x:Name=”BackgroundLayer” Grid.RowSpan=”2″ HorizontalAlignment=”Left”>
  28.                     <Border x:Name=”background” CacheMode=”BitmapCache”>
  29.                         <Grid Background=”Transparent”>
  30.                             <Rectangle Width=”400″ Height=”200″ Fill=”Blue” Opacity=”.5″ HorizontalAlignment=”Left” VerticalAlignment=”Top” Margin=”32″/>
  31.                             <Rectangle Width=”300″ Height=”600″ Fill=”Green” Opacity=”.5″ HorizontalAlignment=”Left” VerticalAlignment=”Top” Margin=”300,128″/>
  32.                             <Rectangle Width=”600″ Height=”100″ Fill=”Red” Opacity=”.5″ HorizontalAlignment=”Left” VerticalAlignment=”Top” Margin=”200,500,0,0″/>
  33.                             <Ellipse x:Name=”indicator” Width=”50″ Height=”50″ Fill=”Orange” Opacity=”.5″ HorizontalAlignment=”Left” VerticalAlignment=”Bottom” Margin=”16″>
  34.                                 <Ellipse.Triggers>
  35.                                     <EventTrigger RoutedEvent=”Ellipse.Loaded”>
  36.                                         <BeginStoryboard>
  37.                                             <Storyboard Storyboard.TargetName=”indicator” Storyboard.TargetProperty=”Opacity”>
  38.                                                 <DoubleAnimation To=”0″ AutoReverse=”True” RepeatBehavior=”Forever”/>
  39.                                             </Storyboard>
  40.                                         </BeginStoryboard>
  41.                                     </EventTrigger>
  42.                                 </Ellipse.Triggers>
  43.                             </Ellipse>
  44.                         </Grid>
  45.                     </Border>
  46.                 </controlsPrimitives:PanningBackgroundLayer>
  47.  
  48.                 <controlsPrimitives:PanningTitleLayer x:Name=”TitleLayer” Grid.Row=”0″ HorizontalAlignment=”Left” Margin=”10,-76,0,9″
  49.                                                         Content=”{TemplateBinding Title} ContentTemplate=”{TemplateBinding TitleTemplate}
  50.                                                         FontSize=”187″ FontFamily=”{StaticResource PhoneFontFamilyLight} CacheMode=”BitmapCache”/>
  51.  
  52.                 <controlsPrimitives:PanningLayer x:Name=”ItemsLayer” Grid.Row=”1″ HorizontalAlignment=”Left”>
  53.                     <ItemsPresenter x:Name=”items”/>
  54.                 </controlsPrimitives:PanningLayer>
  55.  
  56.             </Grid>
  57.         </ControlTemplate>
  58.     </phone:PhoneApplicationPage.Resources>
  59.     
  60.     <Grid x:Name=”LayoutRoot”>
  61.  
  62.         <controls:Panorama Template=”{StaticResource panoramaTemplate} Title=”xaml background”>
  63.             <controls:Panorama.Background>
  64.                 <ImageBrush ImageSource=”dummy.png”/>
  65.             </controls:Panorama.Background>
  66.  
  67.                     <controls:PanoramaItem Header=”home”/>
  68.             <controls:PanoramaItem Header=”item 1″/>
  69.             <controls:PanoramaItem Header=”item 2″/>
  70.         </controls:Panorama>
  71.     
  72.     </Grid>
  73.     
  74. </phone:PhoneApplicationPage>

You can see the Panorama’s Background being set to dummy.PNG (line 64). The new background is set in lines 29-44. Although this doesn’t need it, not having any colors near the edges, you can also apply a margin to prevent seaming. Here there is a reward for the keen observer. You will notice that there is a subtle animation on the Ellipse. Yes, this is a Panorama with an animated background. But please, if you’re going to travel down that path, keep it subtle, and watch that frame rate counter. And beware of wrapping artifacts. The Panorama is not designed to have a dynamic background. And keep both light and dark themes in mind.

PanoramaXamlBackground.zip

Comments (4)

  1. indyfromoz says:

    Hi Dave,

    I have used the code sample your excellent post to customize the Panorama in such a way that the background image is loaded from the disk when the page's Loaded event is triggered. I have used a FadeIn animation to fade in the background image. The XAML for Panorama's ControlTemplate is almost similar to the one you have in your post except for an Image control that is embedded within the Border (line 28). The size of the background image I have used is 1024 x 768 pixels. I found that the fade in animation works fine, the background image loads nicely. However, when I pan through the Panorama items, there is a 200 pixel wide vertical band that appears between the last item and the first item (and between the first item and last item if panning backwards). I have tried using a dummy.png 1024 x 1 pixels but I still see the same behavior. The only way I have been able to avoid the 200 pixel wide vertical strip is to set a Margin = "-224,0" on the Border (line 28 above) that contains the Image control. I would appreciate your thoughts and advice on this issue.

    I have two screencasts that I can link to, they show how setting the margin prevents the vertical strip to appear. I am not sure if I can embed links in comments (I have tried posting this before with the links, they were perhaps blocked by the spam filter). I can send them by email separately.

    Thank you,

    indyfromoz

  2. @indyfromoz – if you can send me a link to a minimal repro app, I can take a look. This might be happening because the Panorama caches a WriteableBitmap of the background, and maybe it is caching the background before it fades in.

  3. indyfromoz says:

    Dave,

    I can't post links to code/sample application I have for you through the comments. I have sent you an email with the Email form available, I hope you receive it.

    Thank you for your help.

    indyfromoz

  4. @indyfromoz – I got your app and was able to repro the issue. It is what I expected. It is faded out when the caches are created. Sorry, I don't have a better workaound than your margin trick.