How to Work with Items Controls  in Windows Phone 7

ItemsControl and its derivatives display collections of items. In addition, Selector and its derivatives implement properties and logic that allow the user to select one or more items from the collection.


This chapter is taken from book "Programming Windows Phone 7" by Charles Petzold published by Microsoft press. http://www.charlespetzold.com/phone/index.html

ItemsControl and its derivatives display collections of items. In addition, Selector and its derivatives implement properties and logic that allow the user to select one or more items from the collection and its derivatives display collections of items. In addition, Selector and its derivatives implement properties and logic that allow the user to select one or more items from the collection. Perhaps the most famous of these controls is ListBox, which has been in Windows-based environments for 25 years. The archetypal ListBox is a scrollable vertical list of items that you can navigate with the keyboard and mouse.

There are three basic ways to get items into an items control: code, XAML, and a data binding.

The code method is demonstrated by the ItemsControlsFromCode project. The program is intended to be displayed in a landscape orientation. It instantiates an ItemsControl, a ListBox, and a ComboBox in three columns of the content Grid:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions> 
            <ItemsControl Name="itemsControl" Grid.Column="0" />           
           
<ListBox Name="listBox" Grid.Column="1" />           
           
<ComboBox Name="comboBox" Grid.Column="2"
                      VerticalAlignment="Top"
                      Foreground="Black" />
        </Grid>

I've added a couple property settings to the ComboBox. Aligning the control at the top of the cell works better with the drop-down feature. I also discovered that the default template forComboBox has not been tweaked for the phone, so setting theForeground property was necessary for the items to be displayed.

The code-behind file fills each of these controls with FontFamily objects:

    public
partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            InitializeComponent(); 
            FillItUp(itemsControl);
            FillItUp(listBox);
            FillItUp(comboBox);
        } 
        void FillItUp(ItemsControl itemsControl)
        {
            string[] fontFamilies =
            {
                "Arial", "Arial Black", "Calibri", "Comic Sans MS",
                "Courier New", "Georgia", "Lucida Sans Unicode",
                "Portable User Interface", "Segoe WP", "Segoe WP Black",
                "Segoe WP Bold", "Segoe WP Light", "Segoe WP Semibold",
                "Segoe WP SemiLight", "Tahoma", "Times New Roman",
                "Trebuchet MS", "Verdana",
"Webdings"
            }; 
            foreach (string fontFamily in fontFamilies)
                itemsControl.Items.Add(new FontFamily(fontFamily));
        }
    }

The Items property defined by ItemsControl is of type ItemCollection and you can put pretty much anything in there that you want. If an object you put in the collection derives from FrameworkElement (such as a Button) then the element displays itself. Otherwise, the item's ToString method is used. Very conveniently, FontFamily has a ToString method that displays the FontFamily name:

sev1.gif

Perhaps the first thing you'll notice about this program is that the ItemsControl doesn't scroll. If you want to scroll an ItemsControl, put it in a ScrollViewer.

The ListBox incorporates its own ScrollViewer. You use your fingers to both scroll and select an item, which is highlighted with the accent color, as Tahoma is highlighted here.

The ComboBox doesn't open until you touch anywhere in the control, and then the list appears:

sev2.gif

You see now why I set the Foreground property to Black. At first I set it to the PhoneBackgroundBrush resource but then I discovered that the ComboBox uses these same colors even with the Light theme.

Because ComboBox badly needs a ControlTemplate to fit in with the Windows Phone 7 aesthetics, I won't be describing the control in this book.

Much of this chapter involves defining templates for items controls, so it will be helpful to look at the visual trees of these three controls to get a sense of their internal architecture.

The ItemsControlsVisualTrees project is very similar to the ItemsControlsFromCode project except that it replaces the ComboBox with another ItemsControl (but this one in a ScrollViewer) and also includes a couple buttons. The program uses that second ItemsControl to display the visual trees associated with the first ItemsControl and the ListBox.

Here's the program displaying the visual tree for the ItemsControl and for the source code please download the link above:

Customizing Item Displays

The second of the three approacesh to filling an items control requires explicitly defining the contents in XAML. The ItemsControlsFromXaml project uses this approach to fill an ItemsControl and two ListBox controls. The Items property defined by ItemsControl is the content property of the control, so in XAML all you need to do is put a bunch of objects between the begin and end tags of the particular items control.

The three TextBlock elements have bindings that reference the R, G, and B properties of the Color property of the brush. Although this ListBox still displays hexadecimal numbers, it at least displays them with a modicum of class:

sev4.gif

Defining a list of items for an items control entirely in XAML is fine for a small number of fixed items; the only reason I used this technique with the 141 Color values is because you can't generate them in Silverlight code by performing reflection on the Colors class. (The Colors class in Silverlight only defines 15 of these colors, so I wrote a WPF program instead that generated the markup that I then pasted into the Silverlight XAML file.)

Note: For the source code of ItemsControlsVisualTrees and ItemsControlsforXAML please download the link given above.

ListBox Selection

Selector (the class from which ListBox and ComboBox derives) defines a SelectedIndex property that indicates the index of the selected item, or the value is -1 if no item is currently selected. Selector also defines a SelectedItem property, which is the item itself, or null if there's no selected item. If SelectedIndex is not equal to -1, SelectedItem is the same as the object returned from the Items property when indexed by SelectedIndex.

The ListBoxSelection program allows a user to pick a Color and a FontFamily from two ListBox controls and displays some text using those selections. The Resources collection contains a standard binding converter and a Style for the ListBox:

    <
phone:PhoneApplicationPage.Resources>
        <petzold:StringFormatConverter x:Name="stringFormat" />
        <Style x:Key="listBoxStyle"
               TargetType="ListBox">
            <Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}" />
            <Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}" />
            <Setter Property="HorizontalAlignment" Value="Center" />
            <Setter Property="Margin" Value="3" />
            <Setter Property="Padding" Value="3" />
        </Style>
    </phone:PhoneApplicationPage.Resources>

All three elements are in a three-row Grid and  TextBlock not in any template at all. Two of its properties are binding targets referencing the two ListBox controls:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions> 
            <ListBox Name="brushListBox"
                     Grid.Row="0"
                     SelectedIndex="0"
                     Style="{StaticResource listBoxStyle}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Rectangle Width="48" Height="36"
                                       Margin="2"
                                       Fill="{Binding}" /> 
                            <StackPanel Orientation="Horizontal"
                                        VerticalAlignment="Center">
                                <TextBlock Text="{Binding Color.R,
                                                          Converter={StaticResource stringFormat},
                                                          ConverterParameter=' {0:X2}'}" />
                                <TextBlock Text="{Binding Color.G,
                                                          Converter={StaticResource stringFormat},
                                                          ConverterParameter='-{0:X2}'}" />
                                <TextBlock Text="{Binding Color.B,
                                                          Converter={StaticResource stringFormat},
                                                          ConverterParameter='-{0:X2}'}" />
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <SolidColorBrush Color="AliceBlue" />
                 ....
                <SolidColorBrush Color="YellowGreen" />
            </ListBox> 
            <ListBox Name="fontFamilyListBox"
                     Grid.Row="1"
                     SelectedIndex="5"
                     Style="{StaticResource listBoxStyle}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}"
                                   FontFamily="{Binding}" />
                    </DataTemplate>
                </ListBox.ItemTemplate> 
                <system:String>Arial</system:String>
                <system:String>Arial Black</system:String>
                <system:String>Calibri</system:String>
                <system:String>Comic Sans MS</system:String>
                <system:String>Courier New</system:String>
                <system:String>Georgia</system:String>
                <system:String>Lucida Sans Unicode</system:String>
                <system:String>Portable User Interface</system:String>
                <system:String>Segoe WP</system:String>
                <system:String>Segoe WP Black</system:String>
                <system:String>Segoe WP Bold</system:String>
                <system:String>Segoe WP Light</system:String>
                <system:String>Segoe WP Semibold</system:String>
                <system:String>Segoe WP SemiLight</system:String>
                <system:String>Tahoma</system:String>
                <system:String>Times New Roman</system:String>
                <system:String>Trebuchet MS</system:String>
                <system:String>Verdana</system:String>
                <system:String>Webdings</system:String>
            </ListBox> 
            <TextBlock Grid.Row="2"
                       Text="Sample Text"
                       FontSize="{StaticResource PhoneFontSizeExtraLarge}"
                       HorizontalAlignment="Center"
                       Margin="12"
                       Foreground="{Binding ElementName=brushListBox,                                            
                                            Path
=SelectedItem}"                      
                       FontFamily="{Binding ElementName=fontFamilyListBox,
                                            Path=SelectedItem}" />
        </Grid> 

When I was first developing this program, it seemed like the FontFamily binding in the DataTemplate was working fine but the FontFamily binding on the bottom TextBlock was causing a nasty runtime exception. I wrote a StringToFontFamilyConverter (which is still in the Petzold.Phone.Silverlight library) but the problem really seemed to be related to a SelectedItem value of null from the ListBox. Once I fixed that problem by explicitly initializing SelectedIndex, the binding problem disappeared.

sev5.gif

Binding to ItemsSource

You've seen how to fill an items control through code or with a list in XAML. You can also set the items using the ItemsSource property defined by ItemsControl. The ItemsSource property is of type IEnumerable so you can pretty much use any collection type, including a simple array. However, if you're dealing with a collection where items can be added or removed dynamically, then it is very common to use the ObservableCollection class, which implements the INotifyCollectionChanged interface. The items control installs a handler for this event to be notified when the collection changes and then updates itself accordingly.

Let's create a ColorPresenter class that can fill up a ListBox with the 140 standard colors (excluding Transparent) by a single binding to ItemsSource and at the same time provide properties that allows displaying these colors in a more user-friendly manner.

It remains a mystery why the Colors class in Silverlight defines only 15 static properties of type Color instead of 141. That makes the ColorPresenter class rather awkward. I already had a WPF program that used reflection on the WPF Colors class, so I adapted that to generate the color names and values that I pulled into this class. Here they are in two static arrays in the ColorPresenter class in the Petzold.Phone.Silverlight library:

using System;
using
System.Text;
using System.Windows.Media;
 
namespace Petzold.Phone.Silverlight
{
    public class ColorPresenter
    {
        static string[] colorNames =
        {
            "AliceBlue","AntiqueWhite","Aqua", "Aquamarine", "Azure",
             ......
            "WhiteSmoke","Yellow", "YellowGreen"
        }; 
        static uint[] uintColors =
        {
            0xFFF0F8FF, 0xFFFAEBD7, 0xFF00FFFF, 0xFF7FFFD4, 0xFFF0FFFF,
            .....
            0xFFF5DEB3, 0xFFFFFFFF, 0xFFF5F5F5, 0xFFFFFF00, 0xFF9ACD32
        }; 
        // Static constructor
        static ColorPresenter()
        {
            Colors = new ColorPresenter[140]; 
            for (int i = 0; i < 140; i++)
            {
                // Break down the color into component
                byte A = (byte)((uintColors[i] & 0xFF000000) >> 24);
                byte R = (byte)((uintColors[i] & 0x00FF0000) >> 16);
                byte G = (byte)((uintColors[i] & 0x0000FF00) >> 8);
                byte B = (byte)((uintColors[i] & 0x000000FF) >> 0); 
                // Create a display name for the color
                StringBuilder builder = new StringBuilder(); 
                foreach (char ch in colorNames[i])
                {
                    if (builder.Length == 0 || Char.IsLower(ch))
                        builder.Append(ch);
                    else
                    {
                        builder.Append(' ');
                        builder.Append(ch);
                    }
                } 
                // Create a ColorPresenter for each color
                ColorPresenter clrPresenter = new ColorPresenter();
                clrPresenter.Color = Color.FromArgb(A, R, G, B);
                clrPresenter.Name = colorNames[i];
                clrPresenter.DisplayName = builder.ToString();
                clrPresenter.Brush = new SolidColorBrush(clrPresenter.Color);
                // Add it to the static array
                Colors[i] = clrPresenter;
            }
        } 
        public static ColorPresenter[] Colors {protected set;get; } 
        public Color Color { protected set; get; } 
        public string Name { protected set; get; } 
        public string DisplayName { protected set; get; } 
        public Brush Brush { protected set; get; } 
        public override string ToString()
        {
            return Name;
        }
    }
}

However, Silverlight doesn't provide a way to access a static property in XAML without instantiating the class containing that property, so the ColorPresenterDemo project includes the ColorPresenter class in its Resources collection:

    <phone:PhoneApplicationPage.Resources>
        <petzold:ColorPresenter x:Key="colorPresenter" />
        <petzold:StringFormatConverter x:Key="stringFormat" />
    </phone:PhoneApplicationPage.Resources> 

The content Grid has just two rows: one for the ListBox and one for a TextBlock with bindings to the ListBox. Notice the ItemsSource property of the ListBox bound to the Colors property of the ColorPresenter resource. With this binding, the ListBox is filled with 140 objects of type ColorPresenter so the DataTemplate can have bindings to the DisplayName and Color properties of that class:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions> 
            <ListBox Grid.Row="0"
                     Name="listBox"
                     ItemsSource="{Binding Source={StaticResource colorPresenter},
                                           Path
=Colors}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
 
                            <Rectangle Grid.Column="0"
                                       Fill="{Binding Brush}"
                                       Width="72" Height="48"
                                       Margin
="2 2 6 2" />
                            <StackPanel Grid.Column="1"
                                        Orientation="Horizontal"
                                        VerticalAlignment="Center">
 
                                <TextBlock Text="{Binding DisplayName}" /> 
                                <TextBlock Text="{Binding Color.R,
                                                          Converter={StaticResource stringFormat},
                                                          ConverterParameter
=' ({0:X2}'}" />
                                <TextBlock Text="{Binding Color.G,
                                                          Converter={StaticResource stringFormat},
                                                          ConverterParameter
='-{0:X2}'}" />
                                <TextBlock Text="{Binding Color.B,
                                                          Converter={StaticResource stringFormat},
                                                          ConverterParameter
='-{0:X2})'}" />
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox> 
            <TextBlock Grid.Row="1"
                       FontSize="{StaticResource PhoneFontSizeExtraLarge}"
                       HorizontalAlignment="Center"
                       Margin="12"            
                       Text="{Binding ElementName=listBox,
                                      Path=SelectedItem.DisplayName}"                    
                       Foreground="{Binding ElementName=listBox,
                                            Path
=SelectedItem.Brush}" />
        </Grid>

The SelectedItem property is also of type ColorPresenter, so the TextBlock can reference properties of ColorPresenter for the bindings to Text and Foreground:

sev6.gif