This blog post describes a simple content control that can be used to defer the rendering of its contents in order to provide a better user experience on Windows Phone 7.
I think anyone who has made the transition from Emulator to Hardware with developing for Windows Phone 7 has experienced the same performance headaches. A couple of areas where performance hits hard is the rendering of ListBoxes and Images downloaded from the web. Thankfully, there are a number of blog posts describing a variety of ways to mitigate the problem. The ListBox performance tips page on the Silverlight WP7 team blog provides links to many useful resources on this topic.
The bottom line is that real WP7 devices take quite a bit longer to render the visual tree, and as developers we have to work around that.
One area in which I think the user experience really suffers is when the user navigates to a new page. If the page contains a reasonably complex UI (perhaps a list of 100 items), it can take quite a long time for the new page to appear. This gives the impression that the phone has locked up and become unresponsive (which I guess it has to a certain extent!). The guys at Telerik noted that using the Panorama control caused a load time of ~1.6 seconds. Whilst the splash screen will be displayed when your application initially loads, giving the user a feeling that the phone is doing something, subsequent page navigations lack any visual cue to indicate that the phone is doing something and hasn’t just died!
DeferredLoadContentControl
The DeferredLoadContentControl
provides a simple solution to this problem, the content that is placed within this control is initially hidden by setting its Visibility
to Collapsed
. Elements that are collapsed do not occupy any layout space, and hence if an element with a long ‘render’ time is collapsed, this time is no longer consumed. When the DeferredLoadContentControl
is initially loaded, it displays a ‘loading…’ indicator, then sets the visibility of its contents to Visible
. When the content is rendered, the loading indicator is hidden.
The images below compare the user experience with and without the DeferredLoadContentControl
:
Whilst this control does not make the page load any quicker, and the phone cannot be interacted with whilst the content is loading, it does mean that the page itself loads much more quickly and hence gives the user the impression that the phone is more responsive.
The code for the DeferredLoadContentControl
is a very simple extension of ContentControl
. The template for this control is shown below:
<Style TargetType="local:DeferredLoadContentControl">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="verticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:DeferredLoadContentControl">
<Grid>
<ContentPresenter x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
<TextBlock x:Name="loadingIndicator"
Text="Loading ..."
VerticalAlignment="Top" HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The template contains a ContentPresenter
which is responsible for displaying the controls content, and a TextBlock
which displays a discrete message while the content is loading. You can of course re-template this control to add a graphical loading indicator, as long as your element has the name "loadingIndicator"
it will be shown / hidden.
The code is also quite simple, here it is in its entirety:
public class DeferredLoadContentControl : ContentControl
{
private ContentPresenter _contentPresenter;
private FrameworkElement _loadingIndicator;
public DeferredLoadContentControl()
{
this.DefaultStyleKey = typeof(DeferredLoadContentControl);
if (!DesignerProperties.IsInDesignTool)
{
this.Loaded += DeferredLoadContentControl_Loaded;
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_contentPresenter = this.GetTemplateChild("contentPresenter") as ContentPresenter;
_loadingIndicator = this.GetTemplateChild("loadingIndicator") as FrameworkElement;
if (!DesignerProperties.IsInDesignTool)
{
_contentPresenter.Visibility = Visibility.Collapsed;
}
else
{
_contentPresenter.Opacity = 0.5;
}
}
private void DeferredLoadContentControl_Loaded(object sender, RoutedEventArgs e)
{
_contentPresenter.Visibility = Visibility.Visible;
_contentPresenter.LayoutUpdated += ContentPresenter_LayoutUpdated;
}
private void ContentPresenter_LayoutUpdated(object sender, EventArgs e)
{
_contentPresenter.LayoutUpdated -= ContentPresenter_LayoutUpdated;
_loadingIndicator.Visibility = Visibility.Collapsed;
}
}
As you can see from the above, the content presenter is initially hidden, when the loaded event is fired, the content is shown. When the LayoutUpdated
event is fired, we know that the content has now been rendered and hide the loading indicator.
Using the control couldn’t be simpler. Just place your slow-to-load content inside the DeferredLoadContentControl
:
<local:DeferredLoadContentControl>
... your content goes here ...
</local:DeferredLoadContentControl>
One nice feature of this control is that when used within the designer, the content contained within the DeferredLoadContentControl
is not hidden, instead it is rendered with a 50% and the loading indicator is shown:
You can download the full source code for this control here.
Regards,
Colin E.