Click here to Skip to main content
Click here to Skip to main content

A Fast Loading Windows Phone 7 NavigationList Control

, 17 May 2011
Rate this:
Please Sign up or sign in to vote.
This blog post describes a Windows Phone 7 NavigationList control, a list control designed for navigation pages. The NavigationList renders twice as fast as a ListBox and has a slightly simpler API.

A few months ago, I blogged about the relative performance of the Windows Phone 7 emulator versus the same code being run on real hardware. There were a couple of take-home messages from this blog post, firstly the performance on real hardware is typically much slower than the emulator, and secondly an ItemsControl can render the same content as a ListBox in less time, making it a better choice for rendering lists of items for navigation.

The recent NoDo updates included some performance improvements, most of which focus on load performance. I re-ran the tests from my previous blog post pre-NoDo and after installing NoDo on the phone (Samsung Omnia), saw no difference in performance between my measurements, which is pretty much what I expected:

Load time is still a significant issue for Windows Phone 7 Silverlight applications, and anything that can be done to reduce this will make your application more slick and useable.

I still see examples on the internet of people using the ListBox for navigation within Windows Phone 7 application, despite the poor performance when compared with an ItemsControl. This is probably because ListBox has a slightly simpler API. In order to combat this, I have come up with a NavigationList control, which is based on an ItemsControl. This control wraps up all the ItemsControl configuration and click / manipulation handling to give a fast and easy-to-use control which simply raises a Navigation event in response to user interactions.

NavigationList Control

The NavigationList control is a lightweight control that can be used to render a list of items (base on an ItemTemplate). The following XAML snippet shows how to use this control:

<l:NavigationList ItemsSource="{Binding}"
              Navigation="NavigationList_Navigation">
  <l:NavigationList.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding}" FontSize="25"/>
    </DataTemplate>
  </l:NavigationList.ItemTemplate>
</l:NavigationList>

The NavigationList has an ItemsSource property which is used to supply your list of items, and an optional ItemTemplate template where you specify how they are rendered.

The control fires a Navigation event whenever the user clicks on an item. This event has an Item argument which contains the item (from the ItemsSource) that was selected. This is typically used to navigate to a new page, as in the example below:

private void NavigationList_Navigation(object sender, NavigationEventArgs e)
{
  // pass the datacontext to the page we are navigating to via the RootVisual.
  FrameworkElement root = Application.Current.RootVisual as FrameworkElement;
  root.DataContext = e.Item;
  NavigationService.Navigate(new Uri("/DetailsPage.xaml", 
                             UriKind.RelativeOrAbsolute));
}

So, how does the NavigationList compare to a ListBox? In terms or performance, it is a clear winner. The example project for this blog post has a test which renders exactly the same content with a ListBox and a NavigationList. Here’s how the load time of the page compares:

A page which uses a NavigationList renders twice as fast as an equivalent page that uses a ListBox!

Another problem with using ListBox is that selection state is ‘persisted’ in the back stack, therefore when you navigate back to the page, you must clear the SelectedItem. Interestingly, I just spotted a blog post by a WP7 developer whose application was rejected during the marketplace submission process for forgetting to do just this!

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
  base.OnNavigatedTo(e);

  // reset selection so that the same item can be re-selected
  navigationListBox.SelectedItem = null;      
}

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  if (navigationListBox.SelectedItem == null)
    return;

  // pass the datacontext to the page we are navigating to via the RootVisual.
  FrameworkElement root = Application.Current.RootVisual as FrameworkElement;
  root.DataContext = navigationListBox.SelectedItem;
  NavigationService.Navigate(new Uri("/DetailsPage.xaml", 
                             UriKind.RelativeOrAbsolute));
}

NavigationList Implementation

The control itself is quite simple; the template includes an ItemsControl which binds to the NavigationList ItemsSource and ItemTemplate dependency properties. The ItemsControl uses a VirtualizingStackPanel for the ItemsPanel to give a faster load time (as does the ListBox):

<Style TargetType="l:NavigationList">
  <Setter Property="ItemTemplate">
    <Setter.Value>
      <DataTemplate>
        <TextBlock Text="{Binding}"/>
      </DataTemplate>
    </Setter.Value>
  </Setter>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate>
        <l:ItemsControlEx x:Name="itemsControl"
              ItemsSource="{Binding Path=ItemsSource, 
                 RelativeSource={RelativeSource TemplatedParent}}"
              ItemTemplate="{Binding Path=ItemTemplate, 
                 RelativeSource={RelativeSource TemplatedParent}}">                        
          <l:ItemsControlEx.ItemsPanel>
            <ItemsPanelTemplate>
              <VirtualizingStackPanel Orientation="Vertical"/>
            </ItemsPanelTemplate>
          </l:ItemsControlEx.ItemsPanel>
          <l:ItemsControlEx.Template>
            <ControlTemplate>
              <ScrollViewer>
                <ItemsPresenter/>
              </ScrollViewer>
            </ControlTemplate>
          </l:ItemsControlEx.Template>
        </l:ItemsControlEx>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

ItemsControlEx is a simple subclass of ItemsControl which raises an event each time an item is added to the panel:

/// <span class="code-SummaryComment"><summary>
</span>/// Extends an ItemsControl, raising an event
/// when the PrepareContainerForItemOverride
/// override is invoked.
/// <span class="code-SummaryComment"></summary>
</span>public class ItemsControlEx : ItemsControl
{
  protected override void PrepareContainerForItemOverride(
            DependencyObject element, object item)
  {
    base.PrepareContainerForItemOverride(element, item);
    OnPrepareContainerForItem(new 
        PrepareContainerForItemEventArgs(element, item));
  }

  /// <span class="code-SummaryComment"><summary>
</span>  /// Occurs when the PrepareContainerForItemOverride method is invoked
  /// <span class="code-SummaryComment"></summary>
</span>  public event EventHandler<PrepareContainerForItemEventArgs> 
               PrepareContainerForItem;

  /// <span class="code-SummaryComment"><summary>
</span>  /// Raises the PrepareContainerForItem event.
  /// <span class="code-SummaryComment"></summary>
</span>  protected void OnPrepareContainerForItem(PrepareContainerForItemEventArgs args)
  {
    if (PrepareContainerForItem != null)
    {
      PrepareContainerForItem(this, args);
    }
  }
}

/// <span class="code-SummaryComment"><summary>
</span>/// Provides data for the PrepareContainerForItem event.
/// <span class="code-SummaryComment"></summary>
</span>public class PrepareContainerForItemEventArgs : EventArgs
{
  public PrepareContainerForItemEventArgs(DependencyObject element, object item)
  {
    Element = element;
    Item = item;
  }

  public DependencyObject Element { get; private set; }

  public object Item { get; private set; }
}

The NavigationControl locates the ItemsControlEx instance from its template to handle this event. Each time an element is added, handlers are added to various events:

public override void OnApplyTemplate()
{
  base.OnApplyTemplate();

  var itemsControl = GetTemplateChild("itemsControl") as ItemsControlEx;
  itemsControl.PrepareContainerForItem += ItemsControl_PrepareContainerForItem;
}

private void ItemsControl_PrepareContainerForItem(object sender, 
             PrepareContainerForItemEventArgs e)
{
  var element = e.Element as UIElement;

  // handle events on the elements added to the ItemsControl
  element.MouseLeftButtonUp += Element_MouseLeftButtonUp;
  element.ManipulationStarted += Element_ManipulationStarted;
  element.ManipulationDelta += Element_ManipulationDelta;
}

The above could have been achieved without the use of ItemsControlEx by providing a container for each item that is added, much the same as the ListBox containing items within a ListBoxItem instance. However, the aim here is to make the control as lightweight as possible, which means minimizing the number of visual elements created.

The Element_MouseLeftButtonUp event handler raises the Navigation event, but what are the manipulation event handlers for? This is because if the NavigationList is used within a control that performs some action due to manipulation, the Pivot control which reacts to swipe for example, a mouse up event is still fired when the manipulation ends. This results in a navigation firing incorrectly, see Tore Lervik’s blog for more details.

private bool _manipulationDeltaStarted;

private void Element_ManipulationDelta(object sender, 
             ManipulationDeltaEventArgs e)
{
  _manipulationDeltaStarted = true;
}

private void Element_ManipulationStarted(object sender, 
             ManipulationStartedEventArgs e)
{
  _manipulationDeltaStarted = false;
}

private void Element_MouseLeftButtonUp(object sender, 
             MouseButtonEventArgs e)
{
  if (_manipulationDeltaStarted)
    return;

  // raises the Navigation event on mouse up,
  // but only if a manipulation delta has not started.
  var element = sender as FrameworkElement;
  OnNavigation(new NavigationEventArgs(element.DataContext));
}

So there you have it, the NavigationList, simple and fast.

Now people … please stop using ListBox for navigation, it is slow and its API is cumbersome!

You can download the source code here: NavigationListControl.zip.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Colin Eberhardt
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.
 
I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.
 
I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.
 
Visit my blog - Colin Eberhardt's Adventures in .NET.
 
Follow me on Twitter - @ColinEberhardt
 
-
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralSome suggestions [modified] PinmemberBernhard Thielen23-May-11 21:25 
GeneralRe: Some suggestions PinmemberColin Eberhardt24-May-11 0:51 
GeneralRe: Some suggestions PinmemberBernhard Thielen25-May-11 23:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 17 May 2011
Article Copyright 2011 by Colin Eberhardt
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid