Working with the LongListSelector Control in Windows Phone Applications

Introduction

ListBox is dead!!! Long live ListBox.

While not completely true, that was the intention of the Windows Phone architects when they introduced LongListSelector control as part of Windows Phone 8.0 operating system. In fact, with the platform tools like Visual Studio 2013, ListBox is not even offered as a control that can be dragged and dropped on the XAML page.

While ListBox worked well for the earlier use cases, there were distinct advantages LongListSelector offered that make the eventual demise inevitable.  We will look at them momentarily, but first let us take a closer look at LongListSelector control.

LongListSelector Control

The LongListSelector control resides in the Microsoft.Phone.Controls namespace in the Microsoft.Phone.dll assembly. Like a ListBox, it is used to display a list of items, but with added support for jump to a specific section of a list.

Key Attributes of LongListSelector

  • LongListSelector can be scrolling as well as non-scrolling.
  • LongListSelector supports vertical scroll bar.
  • It supports JumpList Style for list elements.
  • It supports 2 navigation experiences:
  • Alphabetical list – where letters with no group items are grayed out, and
  • Group headers –where grouping is done by categories, and categories with no items have their group headers grayed out

Advantages of LongListSelector over ListBox

One of the primary advantages of LongListSelector is that it eliminates the requirement of scrolling through entire list to reach a particular spot in a list. LongListSelector sports the support for jumping to a specific point in the list with the JumpList style property.

The second advantage is that LongListSelector performs better than ListBox in user experience because it avoids loading all the elements into display, saving screen real-estate.

When to Choose the LongListSelector

LongListSelector should be chosen when there are more than 8 items in a list. For less than 5 items, grouped RadioButton would be a better control to use.

LongListSelector only supports vertical scrolling, so if you need an experience where the user has to scroll horizontally, LongListSelector should be avoided (ScrollViewer would be a better fit).

Hands-On

Let us build a simple Windows Phone application that demonstrates the use of LongListSelector (which displays the name of a US state).

Create a new Windows Phone 8 application titled WPLongListSelectorDemo in Visual Studio 2013.

New Project
New Project

On the MainPage XAML page, drag and drop a LongListSelector control. Resize it to fit the majority of the page.

Give a name to the LongListSelector control instance you just added. In our walkthrough, we will name it longListSelectorState.

The XAML code at this point is below.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <phone:LongListSelector x:Name="longListSelectorState" HorizontalAlignment="Left" Height="499" Margin="30,51,0,0" VerticalAlignment="Top" Width="376" />
 
        </Grid>

Next, we will create a class called StateList, which will hold the state name as well as its capital.

Open MainPage.xaml.cs and add the following code to create the StateList class.

public class StateList
    {
        public string StateName
        {
            get;
            set;
        }
        public string Capital
        {
            set;
            get;
        }
        public StateList(string stateName, string capital)
        {
            this.StateName = stateName;
            this.Capital = capital;
 
        }
 
    }

We will add a class variable to MainPage class, which will contain a list of StateList instances.

public partial class MainPage : PhoneApplicationPage
    {
        List<StateList> dataSource;
        // Constructor
        public MainPage()

Next, we will create a helper class to initialize the list.

private void InitializeList()
        {
            dataSource = new List<StateList>();
            dataSource.Add(new StateList("Alabama", "Montgomery"));
            dataSource.Add(new StateList("Alaska", "Juneau"));
            dataSource.Add(new StateList("Arizona", "Phoenix"));
            dataSource.Add(new StateList("Arkansas", "Little Rock"));
            dataSource.Add(new StateList("California", "Sacramento"));
            dataSource.Add(new StateList("Colorado", "Denver"));
            dataSource.Add(new StateList("Connecticut", "Hartford"));
            dataSource.Add(new StateList("Delaware", "Dover"));
            dataSource.Add(new StateList("Florida", "Tallahassee"));
            dataSource.Add(new StateList("Georgia", "Atlanta"));
            dataSource.Add(new StateList("Hawaii", "Honolulu"));
            dataSource.Add(new StateList("Idaho", "Boise"));
            dataSource.Add(new StateList("Illinois", "Springfield"));
            dataSource.Add(new StateList("Indiana", "Indianapolis"));
            dataSource.Add(new StateList("Iowa", "Des Moines"));
            dataSource.Add(new StateList("Kansas", "Topeka"));
            dataSource.Add(new StateList("Kentucky", "Frankfort"));
            dataSource.Add(new StateList("Louisiana", "Baton Rouge"));
            dataSource.Add(new StateList("Maine", "Augusta"));
            dataSource.Add(new StateList("Maryland", "Annapolis"));
            dataSource.Add(new StateList("Massachusetts", "Boston"));
            dataSource.Add(new StateList("Michigan", "Lansing"));
            dataSource.Add(new StateList("Minnesota", "Saint Paul"));
            dataSource.Add(new StateList("Mississippi", "Jackson"));
            dataSource.Add(new StateList("Missouri", "Jefferson City"));
            dataSource.Add(new StateList("Montana", "Helena"));
            dataSource.Add(new StateList("Nebraska", "Lincoln"));
            dataSource.Add(new StateList("Nevada", "Carson City"));
            dataSource.Add(new StateList("New Hampshire", "Concord"));
            dataSource.Add(new StateList("New Jersey", "Trenton"));
            dataSource.Add(new StateList("New Mexico", "Santa Fe"));
            dataSource.Add(new StateList("New York", "Albany"));
            dataSource.Add(new StateList("North Carolina", "Raleigh"));
            dataSource.Add(new StateList("North Dakota", "Bismarck"));
            dataSource.Add(new StateList("Ohio", "Columbus"));
            dataSource.Add(new StateList("Oklahoma", "Oklahoma City"));
            dataSource.Add(new StateList("Oregon", "Salem"));
            dataSource.Add(new StateList("Pennsylvania", "Harrisburg"));
            dataSource.Add(new StateList("Rhode Island", "Providence"));
            dataSource.Add(new StateList("South Carolina", "Columbia"));
            dataSource.Add(new StateList("South Dakota", "Pierre"));
            dataSource.Add(new StateList("Tennessee", "Nashville"));
            dataSource.Add(new StateList("Texas", "Austin"));
            dataSource.Add(new StateList("Utah", "Salt Lake City"));
            dataSource.Add(new StateList("Vermont", "Montpelier"));
            dataSource.Add(new StateList("Virginia", "Richmond"));
            dataSource.Add(new StateList("Washington", "Olympia"));
            dataSource.Add(new StateList("West Virginia", "Charleston"));
            dataSource.Add(new StateList("Wisconsin", "Madison"));
            dataSource.Add(new StateList("Wyoming", "Cheyenne"));
           
 
        }

    }

In our MainPage class constructor, we will be calling this.

Next, we will create a helper class that will help convert this list into a grouped list. I have scraped some code from the MSDN help page for LongListSelector and tweaked it to work with our case.

public class StateNameGroup<T> : List<T>
    {
        /// <summary>
        /// The delegate that is used to get the key information.
        /// </summary>
        /// <param name="item">An object of type T</param>
        /// <returns>The key value to use for this object</returns>
        public delegate string GetKeyDelegate(T item);
 
        /// <summary>
        /// The Key of this group.
        /// </summary>
        public string Key { get; private set; }
 
        /// <summary>
        /// Public constructor.
        /// </summary>
        /// <param name="key">The key for this group.</param>
        public StateNameGroup(string key)
        {
            Key = key;
        }
 
        /// <summary>
        /// Create a list of StateNameGroup<T> with keys set by a SortedLocaleGrouping.
        /// </summary>
        /// <param name="slg">The </param>
        /// <returns>Theitems source for a LongListSelector</returns>
        private static List<StateNameGroup<T>> CreateGroups(SortedLocaleGrouping slg)
        {
            List<StateNameGroup<T>> list = new List<StateNameGroup<T>>();
 
            foreach (string key in slg.GroupDisplayNames)
            {
                list.Add(new StateNameGroup<T>(key));
            }
 
            return list;
        }
 
        /// <summary>
        /// Create a list of StateNameGroup<T> with keys set by a SortedLocaleGrouping.
        /// </summary>
        /// <param name="items">The items to place in the groups.</param>
        /// <param name="ci">The CultureInfo to group and sort by.</param>
        /// <param name="getKey">A delegate to get the key from an item.</param>
        /// <param name="sort">Will sort the data if true.</param>
        /// <returns>An items source for a LongListSelector</returns>
        public static List<StateNameGroup<T>> CreateGroups(IEnumerable<T> items, CultureInfo ci, GetKeyDelegate getKey, bool sort)
        {
            SortedLocaleGrouping slg = new SortedLocaleGrouping(ci);
            List<StateNameGroup<T>> list = CreateGroups(slg);
 
            foreach (T item in items)
            {
                int index = 0;
                index = slg.GetGroupIndex(getKey(item));
                if (index >= 0 && index < list.Count)
                {
                    list[index].Add(item);
                }
            }
 
            if (sort)
            {
                foreach (StateNameGroup<T> group in list)
                {
                    group.Sort((c0, c1) => { return ci.CompareInfo.Compare(getKey(c0), getKey(c1)); });
                }
            }
 
            return list;
        }
 
    }

Now, we will work on the UI aspects of the LongListSelector Control.

Under <phone:PhoneApplicationPage>, we will create a sub-node for <phone:PhoneApplicationPage.Resources> where we will define the resources to be used by LongListSelector control.

Our first resource is the DataTemplate for LongListSelector control. We will show the StateName in bold and the Capital in regular text.

<DataTemplate x:Key="StateItemTemplate">
            <StackPanel VerticalAlignment="Top">
                <TextBlock FontWeight="Bold"  Text="{Binding StateName}"  />
                <TextBlock Text="{Binding Capital}" />
            </StackPanel>
        </DataTemplate>

Next, we will define the JumpListStyle resource. We are specifying that the key will be font size 48 (amongst other things)

<Style x:Key="StateJumpListStyle" TargetType="phone:LongListSelector">
            <Setter Property="GridCellSize"  Value="113,113"/>
            <Setter Property="LayoutMode" Value="Grid" />
            <Setter Property="ItemTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Border Width="113" Height="113" Margin="6" >
                            <TextBlock Text="{Binding Key}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="48" Padding="6" VerticalAlignment="Center"/>
                        </Border>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Finally, we define the template for the Group Header which is displayed.

<DataTemplate x:Key="StateGroupHeaderTemplate">
            <Border Background="Transparent" Padding="5">
                <Border Background="{StaticResource PhoneAccentBrush}" BorderBrush="{StaticResource PhoneAccentBrush}" BorderThickness="2" Width="62" 
         Height="62" Margin="0,0,18,0" HorizontalAlignment="Left">
                    <TextBlock Text="{Binding Key}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="48" Padding="6" 
            FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
                </Border>
            </Border>
        </DataTemplate>

In all, the XAML will be look as under after all the modifications.

<phone:PhoneApplicationPage
    x:Class="WPLongListSelectorDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
 
    <phone:PhoneApplicationPage.Resources>
        <DataTemplate x:Key="StateItemTemplate">
            <StackPanel VerticalAlignment="Top">
                <TextBlock FontWeight="Bold"  Text="{Binding StateName}" />
                <TextBlock Text="{Binding Capital}" />
            </StackPanel>
        </DataTemplate>
        <Style x:Key="StateJumpListStyle" TargetType="phone:LongListSelector">
            <Setter Property="GridCellSize"  Value="113,113"/>
            <Setter Property="LayoutMode" Value="Grid" />
            <Setter Property="ItemTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Border Width="113" Height="113" Margin="6" >
                            <TextBlock Text="{Binding Key}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="48" Padding="6" VerticalAlignment="Center"/>
                        </Border>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <DataTemplate x:Key="StateGroupHeaderTemplate">
            <Border Background="Transparent" Padding="5">
                <Border Background="{StaticResource PhoneAccentBrush}" BorderBrush="{StaticResource PhoneAccentBrush}" BorderThickness="2" Width="62" 
         Height="62" Margin="0,0,18,0" HorizontalAlignment="Left">
                    <TextBlock Text="{Binding Key}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="48" Padding="6" 
            FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
                </Border>
            </Border>
        </DataTemplate>
    </phone:PhoneApplicationPage.Resources>
    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <!-- LOCALIZATION NOTE:
            To localize the displayed strings copy their values to appropriately named
            keys in the app's neutral language resource file (AppResources.resx) then
            replace the hard-coded text value between the attributes' quotation marks
            with the binding clause whose path points to that string name.
 
            For example:
 
                Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
 
            This binding points to the template's string resource named "ApplicationTitle".
 
            Adding supported languages in the Project Properties tab will create a
            new resx file per language that can carry the translated values of your
            UI strings. The binding in these examples will cause the value of the
            attributes to be drawn from the .resx file that matches the
            CurrentUICulture of the app at run time.
         -->
 
        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
 
        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <phone:LongListSelector x:Name="longListSelectorState" HorizontalAlignment="Left" Height="499" Margin="30,51,0,0" VerticalAlignment="Top" Width="376" RenderTransformOrigin="-0.893,0.033" ItemTemplate="{StaticResource StateItemTemplate}" JumpListStyle="{StaticResource StateJumpListStyle}" LayoutMode="List" IsGroupingEnabled="true" HideEmptyGroups ="true" GroupHeaderTemplate="{StaticResource StateGroupHeaderTemplate}"/>
 
        </Grid>
 
        <!--Uncomment to see an alignment grid to help ensure your controls are
            aligned on common boundaries.  The image has a top margin of -32px to
            account for the System Tray. Set this to 0 (or remove the margin altogether)
            if the System Tray is hidden.
 
            Before shipping remove this XAML and the image itself.-->
        <!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" Grid.RowSpan="2" IsHitTestVisible="False" />-->
    </Grid>
   
</phone:PhoneApplicationPage>

Finally, we will update the constructor of the MainPage XAML page to call the helper class, which initializes the states list and create a group-based list from that list and specifies it as the ItemsSource for the LongListSelector.

public MainPage()
        {
 
            InitializeList();
            InitializeComponent();
            List<StateNameGroup<StateList>> DataSource = StateNameGroup<StateList>.CreateGroups(dataSource,
                System.Threading.Thread.CurrentThread.CurrentUICulture,
                (StateList s) => { return s.StateName; }, true);
            longListSelectorState.ItemsSource = DataSource;
            // Sample code to localize the ApplicationBar
            //BuildLocalizedApplicationBar();
        }

We are now ready to run our application.

Start screen
Start screen

Panning
Panning

Our Windows Phone application using LongListSelector control is now complete.

Summary

In this article, we learned the basics about LongListSelector control. If you are having trouble following along, you can download the source of this below.

I hope you have found this information useful.

About the Author

Vipul Patel is a Program Manager currently working at Amazon Corporation. He has formerly worked at Microsoft in the Lync team and in the .NET team (in the Base Class libraries and the Debugging and Profiling team). He can be reached at vipul.patel@hotmail.com

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read