Monday 16 April 2012

WP7 Layout Jitters and the SystemTray

Ever since I started on writing WP7 apps, Microsoft has been drilling into developers how important it is to have the UX be fantastic.  Yet, if you create a phone application from the wizard, add another page to the app from the wizard, and hook up an appbar button to navigate to the second page, you end up with a visual mess during navigation.

You’ll find, as I did, that in some cases, your page will appear, and then magically jump down the page by 32 pixels:  The size of the SystemTray.

I posted a question here back at the end of March,: http://forums.create.msdn.com/forums/p/101982/610979.aspx#610979 with a follow  up on how to provoke the problem, as well as a project file that demonstrates the problem here:

http://www.wieser-software.com/m/forums/NavigateHops.zip

If you increase the length of time in the About_LayoutUpdated function’s Sleep call, you should be able to see what I mean.

Now, this has been bothering me for about a month, as I’m just about ready to release a new app, and this looks really bad.

Today, I finally decided I had to waste half a day, and find out what was really going on.

First the facts:

  • The layout jumps by 32 pixels, exactly the height of the SystemTray
  • If you set the opacity of the SystemTray, the height is not removed from the space allocated to your application.
  • If your application supports Portrait or Landscape mode, there’s more work work to do, as in LandscapeLeft orientation, the SystemTray normally reserves 72 pixels on the left, while LandscapeRight reserves 72 pixels on the right.
  • SystemTray.Opacity set in the page XAML is actually a dependency property, as is IsVisible.

 

http://msdn.microsoft.com/en-us/library/microsoft.phone.controls.pageorientation(v=vs.92).aspx has an hilarious description of how (not) to use these flags:

The ideal way to check for orientation in your application is to check the bit flag Portrait, check the bit flag Landscape, or check for both LandscapeLeft and LandscapeRight. However, you should not check for only LandscapeLeft or only LandscapeRight.

(unless of course you really want to know if it’s in LandscapeLeft or LandscapeRight orientation)

So, armed with the facts and misinformation above, lets get coding:

I decided I wanted to build this as a function that can be added to my constructors so that it can be switched off or changed at a later date.

So, first, create a static class in our Framework namespace to hold the WPHacks.  Let’s call it WPHacks.cs

using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Framework
{
// This code writen by Wieser Software Ltd
// www.wieser-software.com
// Use of this code is permitted as is or in derived
// works provided the code is attributed to us.
public static class WPHacks
{
static Thickness PortraitMargin = new Thickness(0, 32, 0, 0);
static Thickness MarginLandscapeLeft = new Thickness(72, 0, 0, 0);
static Thickness MarginLandscapeRight
= new Thickness(0, 0, 72, 0);

/// <summary>
/// Wire up our fixes for SystemTray visibility
/// Make sure you call this after you've called
/// InitializeComponent.
/// </summary>
/// <param name="page">The page to fix</param>
public static void WireOrientationHack(
PhoneApplicationPage page)
{
if (!SystemTray.GetIsVisible(page)) return;

// if using the SystemTray,
// set up the initial margin based on the
// page's desired orientation
PageOrientation o = page.Orientation;
OnOrientationChanged(page,
new OrientationChangedEventArgs(o));

// you may be tempted to use the SystemTray.Opacity
// property instead, but you'd be wrong, because that
// is asking what the current SystemTray is showing
// not what this page wants it to be set to
if (SystemTray.GetOpacity(page) == 1.0)
{
SystemTray.SetOpacity(page, 0.0);
}

page.OrientationChanged += OnOrientationChanged;
}

/// <summary>
/// On an orientation change, we readjust the margins
/// on the page, to leave room for the system tray.
/// 32 Pixels on top for portrait, 72 pixels on left
/// for LandscapeLeft, 72 pixels on right for LandscapeRight
/// </summary>
/// <param name="sender">The page changing orientation</param>
/// <param name="e">Event args</param>
static void OnOrientationChanged(
object sender, OrientationChangedEventArgs e)
{
PhoneApplicationPage src = sender as PhoneApplicationPage;
if (src == null) return;

if (0 != (e.Orientation & PageOrientation.Portrait))
{
src.Margin = PortraitMargin;
}
else
{
src.Margin =
((e.Orientation & PageOrientation.LandscapeLeft)
== PageOrientation.LandscapeLeft)
? MarginLandscapeLeft : MarginLandscapeRight;
}
}
}
}


If you add this code to your project, all that’s required to fix the jitter is to make the following call in your constructor, immediately after calling InitlializeComponent(), as shown in this About constructor:

 

public About()
{
InitializeComponent();
Framework.WPHacks.WireOrientationHack(this);
}


 


 

2 comments:

  1. I also experienced similar problems with navigating back to a page (using phone back button) that contained an async loaded listbox, however I managed to solve this issue by setting SystemTray.IsVisible = true after the listbox was populated (at end of async call back method).

    ReplyDelete
  2. Doesn't that make the page jump down when it becomes visible though?

    ReplyDelete