Sdílet prostřednictvím


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.

Comments

  • Anonymous
    August 31, 2010
    I can't get this solution to build.  I get the following two errors both in VS2010 Express and Blend. Error 1 Predefined type 'System.Object' is not defined or imported Phone.Controls Error 2 Metadata file 'G:Desktop Contentslist-pickerPhone.ControlsBinDebugPhone.Controls.dll' could not be found TestListPicker

  • Anonymous
    August 31, 2010
    It looks like you've lost a reference to the mscorlib. Try to add it manually.

  • Anonymous
    August 31, 2010
    The comment has been removed

  • Anonymous
    August 31, 2010
    The comment has been removed

  • Anonymous
    August 31, 2010
    I've recompiled the solution for the public beta tools. Please re-download it from the blog. Thanks... Alex

  • Anonymous
    August 31, 2010
    The comment has been removed

  • Anonymous
    August 31, 2010
    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. Thanks... Alex

  • Anonymous
    August 31, 2010
    The comment has been removed

  • Anonymous
    August 31, 2010
    Actually, it seems that the selection picked does not stay highlighted.

  • Anonymous
    September 01, 2010
    I've updated the code to fully support beta tools: blogs.msdn.com/.../update-to-list-picker-control.aspx

  • Anonymous
    October 18, 2010
    There is a problem when using a binding on SelectedItem. When the ListBox is shown is doesn’t present the selected item, always the first. To fix it I’ve changed the implementation of ListPicker_SelectionChanged to:            if (base.SelectedIndex > -1)                SelectedIndex = base.SelectedIndex; This seems to work. Do you see any potential problems?

  • Anonymous
    June 07, 2011
    Hi. I have the same problem when using a binding on SelectedItem. When the ListBox is shown is doesn’t present the selected item, always the first. To fix it I’ve changed the implementation of ListPicker_SelectionChanged to but I had the error: System.Windows.ContentControl does not content a definition for SelectedIndex

  • Anonymous
    January 01, 2012
    thanks for sharing this control code

  • Anonymous
    March 12, 2012
    I want to get the text of the selected item in the list picker but i keep getting some other thing like assistantpage1sampledata can any body help me please ??