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 for
ComboBox has not been tweaked for the phone, so setting the
Foreground 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:
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:
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:
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.
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: