Creating List Picker for WP7.

For the application that I am working on right now I needed a control that would allow a user to select a single item from a small list. Similar to how the list picker is defined in the design templates:

This document also specifies that when the user selects one of the entry points, the control should expand to show all available items. Once the user taps on an item to select it, the list picker is then closed. The document also states that this control can have up to 5 items.

The list picker control is not going to be shipped as a part of the Windows Phone Develop Tools, so I've decided to take matters into my own hands and try to create one by myself. I thought that it should be a good excersise for me  to learn how to create a custom control in Silverlight for Window Phone 7 platform and pickup up a few new things in the process.

Looking at the behavior of the list picker in the built-in applications (Setting /Theme) on the emulator, I realized that there's no need to create control from the scratch. I should be able to re-use the ListBox control and animate changing its height at the appropriate times. So I've created a custom control and derived it from the ListBox:

     /// <summary>
    /// The implementation of the inline ListPicker control
    /// </summary>
    public class ListPicker : ListBox
    {
        #region fields
       
        private double itemHeight;
        private bool expanded;
        private int selectedIndex;
        private Storyboard storyboard;
 
        #endregion
 
        #region constructor
      
        public ListPicker()
        {
            this.DefaultStyleKey = typeof(ListPicker);
            base.LayoutUpdated += new EventHandler(ListPicker_LayoutUpdated);
            base.SelectionChanged += new SelectionChangedEventHandler(ListPicker_SelectionChanged);          
        }
 
        #endregion
                // ... more code
      }

I also created an override for the OnMouseLeftButtonUp event:

      protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
     {
          base.OnMouseLeftButtonDown(e);
          // Change background color
          this.Background = new SolidColorBrush(Colors.White);
          if (!expanded)
          {
              // Show border when expanded
              this.BorderThickness = new Thickness(2);
              // Assign previously selected index
              this.SelectedIndex = selectedIndex;
              // Create and begin animation
              this.storyboard = GetDropDownAnimation(this.Height, itemHeight * this.Items.Count);
              this.storyboard.Begin();
           }
           else
           {
              // Hide border
              this.BorderThickness = new Thickness(0);
              // Restore background 
              this.Background = (Brush)(Application.Current.Resources["PhoneTextBoxBrush"]);
              // Unselect an item in the listbox
              this.SelectedIndex = -1;
              // Create and begin animation
              this.storyboard = GetDropDownAnimation(this.Height, itemHeight);
              this.storyboard.Begin();
            }

            expanded = !expanded;
      }   

In the code above I identify the current state of the control whether it's expanded or collapsed, change its backround color and show/hide the border to match to the look of the native control. Then I create an animation with the appropriate parameters for the height of the control.

In order to match the style of my control to the native one I also needed to set some default values in the XAML, so I've added the generic.xaml file to my project. One important note is that this file must be located in the "\Themes" folder of your project in order for it to be picked up by the compiler:

 <ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Phone.Controls;assembly=Phone.Controls">
    
    <Style TargetType="local:ListPicker">
        <Setter Property="Background" Value="{StaticResource PhoneTextBoxBrush}"/>
        <Setter Property="Foreground" Value="{StaticResource PhoneTextBoxForegroundBrush}"/>
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="BorderBrush" Value="Black"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:ListPicker">
                    <ScrollViewer x:Name="ScrollViewer" Foreground="{TemplateBinding Foreground}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
 
</ResourceDictionary>

For this file I re-used the default ListBox's style template and modified a few values for the Backround, Foreground and BorderBrush. I also needed to disable the scrolling in the control this is why the VerticalScrollBarVisibilty property is also set to "Disabled". In order for the control to use this style we also need to assign the DefaultStyleKey property in the constructor of the control as you can see in the first code snippet above.

This is how the sample that's using this control looks like:

 

In addition, the native control has a tilting effect when tapping on items, so I just added the most excellent Peter Torr'sTiltEffect class to my sample project after which my control has become a pretty close to the native one by the looks and behavior.

Feel free to re-use the code for your needs.

Enjoy...

UPDATE: I've uploaded a new version of the control with a few bug fixes.

UPDATE2: Uploaded version that is compiles against public beta tools.

UPDATE3: The beta version of the tools has the bug that causes the OnMouseLeftDown event raised twice. Put the workaround into the code and uploaded the new version.