software solutions for a mobile world

How to Databind SelectedItems of the ListPicker and RecurringDaysPicker

The ListPicker control from the Silverlight Toolkit is one of those Swiss Army Knife controls – very versatile and really useful. It wasn’t quite versatile enough for me though, as one thing it does *not* do is support setting the SelectedItems property when you have the control configured for multiple selections, which is something I wanted to do, and something you will certainly need to do if you ever want to pre-select some items from the list or you want to databind the SelectedItems property to a property in a data object. In this post, I will show you how to extend the ListPicker so that you can set the SelectedItems property. I will also show you how to do the same thing with the RecurringDaysPicker control from the Silverlight Toolkit, which it turns out, is just a ListPicker configured to allow selection from the days of the week.

ListPicker in Single Selection Mode

The ListPicker is a bit like a ComboBox, and to use it you set its ItemsSource property to say, a List<string> or a list of complex objects and if your list has five or fewer items, when the user taps on it, the list expands in-situ, (as shown with the background selector in the following picture), and if your list has more than five items (as with the accent color selector in the picture), then the control expands to what is called ‘Full Mode’, in other words it shows a new screen (right hand screenshot below) with all the items on it and the user can select from the list. And all that user experience is built into the control and comes for free!

ListPickerBasic

In your code of course, you want to know which item the user selected, and the easy way to do this is to get the ListPicker’s SelectedItem or SelectedIndex property. More usually, you would use the SelectedIndex property particularly if you are using MVVM as it is easy to save the SelectedIndex when you serialize your viewmodel class and restore it when you deserialize the next time your user starts your app or the app returns from tombstoning. However, the SelectedIndex property is only useful when the ListPicker is in single selection mode, i.e. has its SelectionMode property set to Single, which is the default.

ListPicker in Multiple Selection Mode

The ListPicker also supports multiple selections from the user. To use it in this mode, you just set the SelectionMode property to Multiple and now when the user taps on the control, the full mode screen displays with checkboxes so the user can select more than one entry from the list. For example, the sample app for this post looks a bit like this, where the Football Teams control is a ListPicker in MultiSelect mode:

ListPickerMulti1ListPickerMulti2

It’s quite simple to program the ListPicker for this kind of usage. First of all, you need to define the templates to use for the list displays, one to be used if the ListPicker items is less than or equal to 5, and one to be used for the full page display whn the number of items is more than that. I’ve defined them in the PhoneApplicationPage.Resources on MainPage.xaml:

    <phone:PhoneApplicationPage.Resources>
        <DataTemplate x:Name="PickerItemTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}" Style="{StaticResource PhoneTextNormalStyle}"/>
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Name="PickerFullModeItemTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}" Style="{StaticResource PhoneTextNormalStyle}"/>
                <TextBlock Text="{Binding League}" Margin="12 0 0 0" Style="{StaticResource PhoneTextSubtleStyle}"/>
            </StackPanel>
        </DataTemplate>
    </phone:PhoneApplicationPage.Resources>

And you then assign those templates where you declare the ListPicker control:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TextBlock Height="30" HorizontalAlignment="Left" Margin="12,73,0,0" Name="textBlock1" Text="Multiselect ListPicker:" VerticalAlignment="Top" />             
<toolkit:ListPicker Height="100" HorizontalAlignment="Left" Margin="9,109,0,0" x:Name="listPicker1" VerticalAlignment="Top" Width="441" SelectionMode="Multiple" ItemsSource="{Binding FootballTeams}" Header="Football Teams" FullModeHeader="Football Teams" ItemTemplate="{StaticResource PickerItemTemplate}" FullModeItemTemplate="{StaticResource PickerFullModeItemTemplate}"/> <toolkit:RecurringDaysPicker x:Name="recurringDaysPicker1" Margin="198,259,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="212"/> <TextBlock Height="41" HorizontalAlignment="Left" Margin="12,275,0,0" Name="textBlock2" Text="Recurring Days:" VerticalAlignment="Top" Width="166" /> <Button Content="Set ViewModel Props" Height="93" HorizontalAlignment="Left" Margin="71,433,0,0" Name="button1" VerticalAlignment="Top" Width="306" Click="button1_Click" /> </Grid>

You’ll notice that the templates contain TextBlock controls that are databound to fields Name and League, and the ItemsSource for the ListPicker is bound to something called FootballTeams. FootballTeams is a property of type List<FootballTeam> on my MainViewModel class:

using System.Collections.Generic;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace SelectedItemsDataBindSample
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public const string FootballTeamsPropertyName = "FootballTeams";
        private List<FootballTeam> _footballTeams = new List<FootballTeam>();

        public List<FootballTeam> FootballTeams
        {
            get
            {
                return _footballTeams;
            }
            set
            {
                if (_footballTeams == value)
                {
                    return;
                }

                _footballTeams = value;
                RaisePropertyChanged(SelectedTeamsPropertyName);
            }
        }

        public MainViewModel()
        {
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

The FootballTeam class looks like this:

public class FootballTeam
{
    public string Name { get; set; }
    public string League { get; set; }

    public FootballTeam()
    {  }

    public FootballTeam(string name, string league)
    {
        Name = name;
        League = league;
    }
}

The sample app creates an instance of MainViewModel in my App.Xaml.cs, and exposes it via a property called ViewModel:

public partial class App : Application
{
    private static MainViewModel viewModel = null;

    /// <summary>
    /// A static ViewModel used by the views to bind against.
    /// </summary>
    /// <returns>The MainViewModel object.</returns>
    public static MainViewModel ViewModel
    {
        get
        {
            // Delay creation of the view model until necessary
            if (viewModel == null)
            {
                viewModel = new MainViewModel();
                viewModel.FootballTeams = new List<FootballTeam>()
                                            {
                                                new FootballTeam("Leeds", "Championship"),
                                                new FootballTeam("Man U", "Premiership"),
                                                new FootballTeam("Man C", "Premiership"),
                                                new FootballTeam("Chelsea", "Premiership"),
                                                new FootballTeam("Liverpool", "Premiership"),
                                                new FootballTeam("Spurs", "Premiership"),
                                            };
            }

            return viewModel;
        }
    }
}    

and the final piece of the jigsaw is to set this instance of MainViewModel to be the DataContext for MainPage.xaml. Notice also that I have set the SummaryForSelectedItemsDelegate property to a method I have written called SummarizeTeams. This is how you provide the ‘summary text’ representation of the users’ selections which is displayed on the MainPage, on the ‘normal’ rendering of the ListPicker before the user interacts with it, by providing a method like this that takes an IList parameter and returns a string:

public partial class MainPage : PhoneApplicationPage
{
    // Constructor
    public MainPage()
    {
        InitializeComponent();
        this.DataContext = App.ViewModel;

        this.listPicker1.SummaryForSelectedItemsDelegate = SummarizeTeams;
    }

    protected string SummarizeTeams(IList selection)
    {
        string str = "*None Selected*";

        if (null != selection && selection.Count > 0)
        {
            StringBuilder contents = new StringBuilder();
            int idx = 0;
            foreach (object o in selection)
            {
                if (idx > 0) contents.Append(", ");
                contents.Append(((FootballTeam)o).Name);
                idx++;
            }
            str = contents.ToString();
        }

        return str;
    }
}
 

Getting – and Setting – the SelectedItems

After a user has selected items from the ListPicker, you can easily find out what the selected items are by hooking the SelectionChanged event and in the event handler, enumerate the SelectedItems collection. For example:

private void listPicker1_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
    StringBuilder msg = new StringBuilder();
    msg.Append("Selected Teams:");

    if (listPicker1.SelectedItems != null)
    {
        foreach (var item in listPicker1.SelectedItems)
        {
            var team = item as FootballTeam;
            msg.Append(" " + team.Name);
        }
    }

    System.Diagnostics.Debug.WriteLine(msg.ToString());
}

Extending the ListPicker to Support Setting of SelectedItems

Using the standard control, there’s no way to programmatically set the SelectedItems, either by setting the SelectedItems property directly, or by exposing a collection of selected objects through a property of a ViewModel class and using databinding. This is because the SelectedItems property is a read-only property, as revealed if you browse the control class in the Object Browser:

ListPickerObjectBrowser

So if you want to set the SelectedItems, we must change the implementation of this property and add a setter to it as well.

There are two ways to do this:

  1. Download the source for the silverlight toolkit from http://silverlight.codeplex.com and modify it yourself so you run with a customised version
  2. Create your own custom control that inherits from ListPicker and implement the altered SelectedItems property in there.

I prefer option 2, as it turns out the code you need to write is pretty simple, and it’s a simpler solution – you can just add your reference to the Silverlight Toolkit assembly through NuGet or directly, and then inherit from the standard ListPicker in your custom control.

The code you need to add a setter to the SelectedItems property looks like this:

using Microsoft.Phone.Controls;
using System.Collections;

namespace SelectedItemsDataBindSample
{
    public class ListPickerEx : ListPicker
    {
        /// <summary>
        /// Gets or sets the selected items.
        /// </summary>
        public new IList SelectedItems
        {
            get
            {
                return (IList)GetValue(SelectedItemsProperty);
            }
            set
            {
                base.SetValue(SelectedItemsProperty, value);
            }
        }
    }
}

That’s it! You can do the same for the RecurringDaysPicker with identical code, and this is demonstrated as well in the sample code for this post.

To use your custom control, first declare the namespace for your custom controls in the Xaml at the top of your page (in this example, these controls are just in the SelectedItemsDataBindSample namespace, in the project assembly):

<phone:PhoneApplicationPage 
    x:Class="SelectedItemsDataBindSample.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"
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
    xmlns:local="clr-namespace:SelectedItemsDataBindSample"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True" 
    >

and where you declare the ListPicker, simply substitute local:ListPickerEx for toolkit:ListPicker:

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <local:ListPickerEx Height="100" HorizontalAlignment="Left" Margin="9,109,0,0" x:Name="listPicker1" VerticalAlignment="Top" Width="441" 
                                SelectionMode="Multiple" ItemsSource="{Binding FootballTeams}"
                                SelectedItems="{Binding SelectedTeams, Mode=TwoWay}"
                                SelectionChanged="listPicker1_SelectionChanged"
                                Header="Football Teams" FullModeHeader="Football Teams" 
                                ItemTemplate="{StaticResource PickerItemTemplate}" FullModeItemTemplate="{StaticResource PickerFullModeItemTemplate}"/>
            <TextBlock Height="30" HorizontalAlignment="Left" Margin="12,73,0,0" Name="textBlock1" Text="Multiselect ListPicker:" VerticalAlignment="Top" />
            <local:RecurringDaysPickerEx x:Name="recurringDaysPicker1" Margin="198,259,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="212" 
                                         SelectedItems="{Binding SelectedDays, Mode=TwoWay}" />
            <TextBlock Height="41" HorizontalAlignment="Left" Margin="12,275,0,0" Name="textBlock2" Text="Recurring Days:" VerticalAlignment="Top" Width="166" />
            <Button Content="Set ViewModel Props" Height="93" HorizontalAlignment="Left" Margin="71,433,0,0" Name="button1" VerticalAlignment="Top" Width="306" Click="button1_Click" />
            <Button Content="Set SelectedItems" Height="93" HorizontalAlignment="Left" Margin="71,514,0,0" Name="button2" VerticalAlignment="Top" Width="306" Click="button2_Click" />
        </Grid>

Now you could have code similar to the following to programmatically set the SelectedItems:

// Get the full collection of items:
List<FootballTeam> allItems = listPicker1.ItemsSource as List<FootballTeam>;

// Set the SelectedItems to the 5th and 6th teams
listPicker1.SelectedItems = new ObservableCollection<object>() { allItems[4], allItems[5] };
 

The key thing to remember here is that the collection of items you set the SelectedItems property to must be a collection containing zero, 1 or more of the exact same objects that are in the main list of items that you supplied for the ItemsSource. The only qualification here is that if your data objects are just simple strings, as is the case with a RecurringDaysPicker for example, then you can just supply a collection of strings for SelectedItems as well – as long as the supplied strings exist in the ItemsSource as well of course.

Using Two-Way DataBinding with SelectedItems

You can also databind the SelectedItems property to a property in a View Model class. The trick here is to declare the property in your view model as type ObservableCollection<object>. In my  sample code for this post, I started out by doing the obvious thing of defining a property of type ObservableCollection<FootballTeam>, but it turns out that doesn’t work. Internally the control expresses its list of selected items as an ObservableCollection<object> so when you declare two-way data binding and the control tries to set the bound property in the View Model, it doesn’t know how to convert between an ObservableCollection<object> and an ObservableCollection<FootballTeam> – so just declare the bindable property in your ViewModel as ObservableCollection<object> and everything works fine.

In the FootballTeam sample, the ViewModel class now has a new public property called SelectedTeams:

public class MainViewModel : INotifyPropertyChanged
{
    ...

    /// <summary>
    /// The <see cref="SelectedDays" /> property's name.
    /// </summary>
    public const string SelectedTeamsPropertyName = "SelectedTeams";

    private ObservableCollection<object> _mySelectedTeams = new ObservableCollection<object>();

    /// <summary>
    /// Sets and gets the SelectedTeams property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public ObservableCollection<object> SelectedTeams
    {
        get
        {
            return _mySelectedTeams;
        }
        set
        {
            if (_mySelectedTeams == value)
            {
                return;
            }

            _mySelectedTeams = value;
            RaisePropertyChanged(SelectedTeamsPropertyName);
        }
    }
    
    ...
}

And in the MainPage.xaml, the SelectedItems property is databound to the new property:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <local:ListPickerEx Height="100" HorizontalAlignment="Left" Margin="9,109,0,0" x:Name="listPicker1" VerticalAlignment="Top" Width="441" 
                                SelectionMode="Multiple" ItemsSource="{Binding FootballTeams}"
                                SelectedItems="{Binding SelectedTeams, Mode=TwoWay}"
                                SelectionChanged="listPicker1_SelectionChanged"
                                Header="Football Teams" FullModeHeader="Football Teams" 
                                ItemTemplate="{StaticResource PickerItemTemplate}" FullModeItemTemplate="{StaticResource PickerFullModeItemTemplate}"/>
           ...
        </Grid>

If you run this sample and tap on the ListPickerEx on the screen and then select some teams, the control sets the SelectedTeams property of the underlying MainViewModel instance. If you tap on the Set ViewModel Props button, the code behind directly sets the SelectedTeams property of the MainViewModel object, and through the magic of databinding, the UI updates. The sample also does the same thing for a RecurringDaysPicker for which the solution is virtually identical.

This sample also implements proper support for tombstoning and saving and restoring state when closed and later re-opened. To do this, it (de)serializes the MainViewModel object to and from isolated storage. This raises more interesting questions on how best to serialize an object that has a property (SelectedTeams) that exposes a list of object references rather than actual object instances. This is demonstrated in the sample code, but the explanation will be the subject for my next post…

Download the sample code


Posted Feb 02 2012, 09:41 AM by Andy Wigley

Comments

Community Blogs wrote Windows Dev News for February 02, 2012 - #0035
on 02-03-2012 0:41

body { font-size: 11pt; font-family: 'Tahoma'; margin:0; padding:0; height:100%;} Windows Dev News for

How to Databind SelectedItems of the ListPicker and RecurringDaysPicker wrote How to Databind SelectedItems of the ListPicker and RecurringDaysPicker
on 02-03-2012 7:24

Pingback from  How to Databind SelectedItems of the ListPicker and RecurringDaysPicker

Dew Drop – February 3, 2012 (#1,258) | Alvin Ashcraft's Morning Dew wrote Dew Drop &ndash; February 3, 2012 (#1,258) | Alvin Ashcraft&#039;s Morning Dew
on 02-03-2012 9:11

Pingback from  Dew Drop – February 3, 2012 (#1,258) | Alvin Ashcraft's Morning Dew

Copyright © 2011 APPA Mundi Limited. All Rights Reserved. Terms of Use and Privacy Policy. OrcsWeb's Windows Cloud Server Hosting