A Fast Loading Windows Phone 7 NavigationList Control

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 the 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 them 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), but 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 who's 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:

/// <summary>
/// Extends an ItemsControl, raising an event when the PrepareContainerForItemOverride
/// override is invoked.
/// </summary>
public class ItemsControlEx : ItemsControl
{
  protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
  {
    base.PrepareContainerForItemOverride(element, item);

    OnPrepareContainerForItem(new PrepareContainerForItemEventArgs(element, item));
  }

  /// <summary>
  /// Occurs when the PrepareContainerForItemOverride method is invoked
  /// </summary>
  public event EventHandler<PrepareContainerForItemEventArgs> PrepareContainerForItem;

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

/// <summary>
/// Provides data for the PrepareContainerForItem event.
/// </summary>
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 was as the ListBox contains 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 it's API is cumbersome!

You can download the sourcecode here:NavigationListControl.zip

Regards, Colin E.

blog comments powered by Disqus