How to Databind SelectedItems of the ListPicker and RecurringDaysPicker

【转】【部分译】 源地址 :http://mobileworld.appamundi.com/blogs/andywigley/archive/2012/02/02/how-to-databind-selecteditems-of-the-listpicker-and-recurringdayspicker.aspx

Windows Phone Toolkits 中ListPicker的一些常用操作。

  1. ListPicker in Single Selection Mode (ListPicker单选模式)

  2. ListPicker in Multiple Selection Mode (ListPicker复选模式)

  3. Getting the SelectedItems (获取ListPicker选中项目)

  4. Extending the ListPicker to Support Setting of SelectedItems (扩展ListPicker以支持设置默认项)

  5. Using Two-Way DataBinding with SelectedItems (在SelectedItems中使用 Two_Way模式)

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 RecurringDaysPickercontrol 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!

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:

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:

 1     <phone:PhoneApplicationPage.Resources>
 2         <DataTemplate x:Name="PickerItemTemplate">
 3             <StackPanel Orientation="Horizontal">
 4                 <TextBlock Text="{Binding Name}" Style="{StaticResource PhoneTextNormalStyle}"/>
 5             </StackPanel>
 6         </DataTemplate>
 7         <DataTemplate x:Name="PickerFullModeItemTemplate">
 8             <StackPanel Orientation="Horizontal">
 9                 <TextBlock Text="{Binding Name}" Style="{StaticResource PhoneTextNormalStyle}"/>
10                 <TextBlock Text="{Binding League}" Margin="12 0 0 0" Style="{StaticResource PhoneTextSubtleStyle}"/>
11             </StackPanel>
12         </DataTemplate>
13     </phone:PhoneApplicationPage.Resources>

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

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

 1 using System.Collections.Generic;
 2 using System.ComponentModel;
 3 using System.Collections.ObjectModel;
 4 
 5 namespace SelectedItemsDataBindSample
 6 {
 7     public class MainViewModel : INotifyPropertyChanged
 8     {
 9         public const string FootballTeamsPropertyName = "FootballTeams";
10         private List<FootballTeam> _footballTeams = new List<FootballTeam>();
11 
12         public List<FootballTeam> FootballTeams
13         {
14             get
15             {
16                 return _footballTeams;
17             }
18             set
19             {
20                 if (_footballTeams == value)
21                 {
22                     return;
23                 }
24 
25                 _footballTeams = value;
26                 RaisePropertyChanged(SelectedTeamsPropertyName);
27             }
28         }
29 
30         public MainViewModel()
31         {
32         }
33 
34         public event PropertyChangedEventHandler PropertyChanged;
35 
36         private void RaisePropertyChanged(string propertyName)
37         {
38             if (PropertyChanged != null)
39             {
40                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
41             }
42         }
43     }
44 }



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:


 1 public partial class App : Application
 2 {
 3     private static MainViewModel viewModel = null;
 4 
 5     /// <summary>
 6     /// A static ViewModel used by the views to bind against.
 7     /// </summary>
 8     /// <returns>The MainViewModel object.</returns>
 9     public static MainViewModel ViewModel
10     {
11         get
12         {
13             // Delay creation of the view model until necessary
14             if (viewModel == null)
15             {
16                 viewModel = new MainViewModel();
17                 viewModel.FootballTeams = new List<FootballTeam>()
18                                             {
19                                                 new FootballTeam("Leeds", "Championship"),
20                                                 new FootballTeam("Man U", "Premiership"),
21                                                 new FootballTeam("Man C", "Premiership"),
22                                                 new FootballTeam("Chelsea", "Premiership"),
23                                                 new FootballTeam("Liverpool", "Premiership"),
24                                                 new FootballTeam("Spurs", "Premiership"),
25                                             };
26             }
27 
28             return viewModel;
29         }
30     }
31 }    


 

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:


 1 private void listPicker1_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
 2 {
 3     StringBuilder msg = new StringBuilder();
 4     msg.Append("Selected Teams:");
 5 
 6     if (listPicker1.SelectedItems != null)
 7     {
 8         foreach (var item in listPicker1.SelectedItems)
 9         {
10             var team = item as FootballTeam;
11             msg.Append(" " + team.Name);
12         }
13     }
14 
15     System.Diagnostics.Debug.WriteLine(msg.ToString());
16 }


 

Extending the ListPicker to Support Setting of SelectedItems

如果使用SilverLight Toolkits for Windows Phone中的ListPicker控件的话,是没法设置SelectedItems的

1 //
2 // Summary:
3 //     Gets the selected items.
4 public IList SelectedItems { get; }

 可以通过继承并修改ListPicker来实现这个功能

 1 using Microsoft.Phone.Controls;
 2 using System.Collections;
 3 
 4 namespace SelectedItemsDataBindSample
 5 {
 6     public class ListPickerEx : ListPicker
 7     {
 8         /// <summary>
 9         /// Gets or sets the selected items.
10         /// </summary>
11         public new IList SelectedItems
12         {
13             get
14             {
15                 return (IList)GetValue(SelectedItemsProperty);
16             }
17             set
18             {
19                 base.SetValue(SelectedItemsProperty, value);
20             }
21         }
22     }
23 }

 TestPage.xmal.cs

 public static List<string> items = new List<string> { "天气状况", "温度", "风向" };
itemPicker.ItemsSource = items;
itemPicker.SelectedItems = items;
itemPicker.SummaryForSelectedItemsDelegate = (i) =>
{
......
}

这样就可以实现给ListPicker设置默认值了。

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:

View Code
 1 public class MainViewModel : INotifyPropertyChanged
 2 {
 3     ...
 4 
 5     /// <summary>
 6     /// The <see cref="SelectedDays" /> property's name.
 7     /// </summary>
 8     public const string SelectedTeamsPropertyName = "SelectedTeams";
 9 
10     private ObservableCollection<object> _mySelectedTeams = new ObservableCollection<object>();
11 
12     /// <summary>
13     /// Sets and gets the SelectedTeams property.
14     /// Changes to that property's value raise the PropertyChanged event. 
15     /// </summary>
16     public ObservableCollection<object> SelectedTeams
17     {
18         get
19         {
20             return _mySelectedTeams;
21         }
22         set
23         {
24             if (_mySelectedTeams == value)
25             {
26                 return;
27             }
28 
29             _mySelectedTeams = value;
30             RaisePropertyChanged(SelectedTeamsPropertyName);
31         }
32     }
33     
34     ...
35 
36 }

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

View Code
1  <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
2             <local:ListPickerEx Height="100" HorizontalAlignment="Left" Margin="9,109,0,0" x:Name="listPicker1" VerticalAlignment="Top" Width="441" 
3                                 SelectionMode="Multiple" ItemsSource="{Binding FootballTeams}"
4                                 SelectedItems="{Binding SelectedTeams, Mode=TwoWay}"
5                                 SelectionChanged="listPicker1_SelectionChanged"
6                                 Header="Football Teams" FullModeHeader="Football Teams" 
7                                 ItemTemplate="{StaticResource PickerItemTemplate}" FullModeItemTemplate="{StaticResource PickerFullModeItemTemplate}"/>
8            ...
9         </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…

原文地址:https://www.cnblogs.com/NailClipper/p/2698750.html