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]()