This is part of a series on migrating from the WP Toolkit.
With the release of the new Windows Phone 8.1 XAML (Runtime) SDK comes a lot of new controls that were previously only available in the Windows Phone Toolkit. One of these controls in the ComboBox. Well, this control isn’t new, but now it works as one would expect. In Windows Phone 8, the ComboBox displayed more of a ListBox than a ComboBox. It would display all items and not a dropdown. Getting the drop down like functionality required you to use a third party control like the Windows Phone Toolkit ListPicker control. This control would have a dropdown if the number of items was limited, and would show full screen for large number of items. The new ComboBox helps us scratch an itch, but it does not match up to the ListPicker from the toolkit. Before we get into the things the ComboBox does not do, let’s get into the things is does do, and how you can modify your code to start using the ComboBox if you are used to using the ListPicker.
The ComboBox works great for the scenario where you have a list of text you want to show. It works great when the content you want to show will be the same in the three states of the control.
In the above examples the content is displayed the same in all three states, collapsed, expanded (dropdown open), and full screen.
Let’s assume you were displaying something like this in a Windows Phone 7 or 8 app and that you used the ListPicker. If you do have this simple scenario, then all you need to do s swap “toolkit:ListPicker” for “ComboBox” in your XAML.
<!-- from -->
<toolkit:ListPicker Header="List Picker" ItemsSource="{Binding Items}">
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
</toolkit:ListPicker>
<!-- to -->
<ComboBox Header="Combo picker" ItemsSource="{Binding Items}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
With both controls, if the count of the items is six or more, it would expand to full screen.
You can get a little complex with the CombBoBox ItemTemplate. If you wanted to display colors with their text you could set the ItemTemplate to the following:
<StackPanel Orientation="Horizontal">
<Rectangle Width="20" Height="20" >
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Color}" />
</Rectangle.Fill>
</Rectangle>
<TextBlock Text="{Binding Name}" Margin="12,0"/>
</StackPanel>
That’s about it for the simple use cases. Now, where the ComboBox falls short of the ListPicker
- No way to set that it should expand to full screen only (ExpansionMode from ListPicker)
- No way to set a template when in full screen (FullModeItemTemplate from ListPicker)
- No way to set the header when in full screen (FullModeHeader from ListPicker)
- No way to set the number of items the ComboBox should contain before showing in full screen (ItemCountThreshold from ListPicker)
- Cannot have a picker that is only shown in full screen (does not display the “button”). This is useful when you want to display a picker based on clicking an app bar button
We can work through most of these shortcomings, but it requires some extra work. To show some workarounds for the above items, we’ll work to create a ListPicker that will display the following no matter how many items are in the collection.
It’s important to note that we will not be using the ComboBox to display the above. With this sample we will cover items 1, 2, and 3. To display a custom picker, we’ll start with a standard button and set it’s Flyout to be a ListPickerFlyout. The button will need to be stretched and the content needs to display on the left, so we need to set the HorizontalAlignment and HorizontalContentAlignment property.
<Button HorizontalAlignment="Stretch" HorizontalContentAlignment="Left">
<Button.Flyout>
<ListPickerFlyout >
</ListPickerFlyout>
</Button.Flyout>
</Button>
When the button is tapped, it will open the flyout that is set. The ListPickerFlyout works just like an Selector control. It has an ItemsSource, SelectedItem, SelectedIndex and more. However, instead of a SelectedItemChanged event, it has a ItemsPicked event. The ListPickerFlyout is not a control so you cannot place it in the visual tree like you would a ListBox,, TimePicker, etc. Instead it can only be set within a Flyout property. It is a DependencyObject, so you can use binding with it.
We’ll setup the ListPickerFlyout to display our custom header and custom template
<ListPickerFlyout Title="SELECT CITY" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCity, Mode=TwoWay}">
<ListPickerFlyout.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding City}" FontSize="{StaticResource TextStyleExtraLargeFontSize}"/>
<TextBlock Text="{Binding State}" FontSize="{StaticResource TextStyleLargeFontSize}"/>
<TextBlock Text="{Binding TimeZone}" FontSize="{StaticResource TextStyleMediumFontSize}"/>
</StackPanel>
</DataTemplate>
</ListPickerFlyout.ItemTemplate>
</ListPickerFlyout>
Now we’ll need to set the content of the button to display the the selected city.
<TextBlock DataContext="{Binding SelectedCity}">
<Run Text="{Binding City}"/><Run Text=","/>
<Run Text="{Binding StateAbr}"/>
</TextBlock>
So the complete XAML for our custom ListPicker is:
<Button HorizontalAlignment="Stretch" HorizontalContentAlignment="Left">
<TextBlock DataContext="{Binding SelectedCity}">
<Run Text="{Binding City}"/><Run Text=","/>
<Run Text="{Binding StateAbr}"/>
</TextBlock>
<Button.Flyout>
<ListPickerFlyout x:Name="CityPicker" Title="SELECT CITY" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCity, Mode=TwoWay}">
<ListPickerFlyout.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding City}" FontSize="{StaticResource TextStyleExtraLargeFontSize}"/>
<TextBlock Text="{Binding State}" FontSize="{StaticResource TextStyleLargeFontSize}"/>
<TextBlock Text="{Binding TimeZone}" FontSize="{StaticResource TextStyleMediumFontSize}"/>
</StackPanel>
</DataTemplate>
</ListPickerFlyout.ItemTemplate>
</ListPickerFlyout>
</Button.Flyout>
</Button>
Unlike the ComboBox, the Button does not have a Header property, so if you want to display a header for the picker you will need to add one.
<StackPanel>
<TextBlock Text="Expanding picker"
Foreground="{StaticResource PhoneMidBrush}"
FontSize="{ThemeResource TextStyleMediumFontSize}"
Margin="0,0,0,-4.5" />
<Button />
</StackPanel/>
Not to much work, and we were able to solve the following drawbacks of the ComboBox
- SOLVED: No way to set that it should expand to full screen only (ExpansionMode from ListPicker)
- SOLVED: No way to set a template when in full screen (FullModeItemTemplate from ListPicker)
- SOLVED: No way to set the header when in full screen (FullModeHeader from ListPicker)
Which leaves us with the following items unsolved
- No way to set the number of items the ComboBox should contain before showing in full screen (ItemCountThreshold from ListPicker)
- Cannot have a picker that is only shown in full screen (does not display the “button”). This is useful when you want to display a picker based on clicking an app bar button
Unfortunately there is no way to solve number 1 without writing your own ComboBox, I won’t go into that as I don’t see this item being a big deal. We can solve the last one. In Windows Phone 8 you could put a button (or a menu item) in the app bar and when it was tapped, show a ListPicker. Take the example of a share button in your app. I would solve this by doing the following:
<toolkit:ListPicker x:Name="SharePicker"
ExpansionMode="FullScreenOnly"
Visibility="Collapsed"
FullModeHeader="SHARE"
SelectionChanged="SharePickerSelectionChanged">
<toolkit:ListPicker.FullModeItemTemplate>
<DataTemplate>
<TextBlock Margin="0,20" Text="{Binding Name}"
Style="{StaticResource PhoneTextExtraLargeStyle}" />
</DataTemplate>
</toolkit:ListPicker.FullModeItemTemplate>
</toolkit:ListPicker>
Notice that the ExpansionMode is FullScreenOnly and the Visibility is Collapsed. This would hide the button look of the picker, but would still allow it to be shown when the Open method is called.
private void OnShareButtonClick(object sender, EventArgs e)
{
SharePicker.Open();
}
Pretty simple, but you cannot accomplish this with the ComboBox. Once again you can accomplish this the ListPickerFlyout. You’ll want to define the ItemTemplate in the resources of the page so that it can be accessed from your code. When the button is clicked, show a picker.
private void OnAppBarButtonClick(object sender, RoutedEventArgs e)
{
var picker = new ListPickerFlyout();
picker.ItemsSource = Items;
picker.ItemTemplate = (DataTemplate)Resources["PickerTemplate"];
picker.ItemsPicked += OnItemsPicked;
picker.ShowAt(this);
}
Even though the ComboBox does not do as much as the ListPicker did, we are able to easily get those missing items accomplished!