3. Improving Scrolling Performance
When binding against collections on the phone, you must be aware of
the implications of how large amounts of data can affect the performance
of the scrolling of list controls (e.g., ListBox
, ScrollViewer
, ItemsSource
, etc.). There are two minor tweaks you can make to improve this performance: image creation and scroll handling.
For image creation, you can decide how images are actually loaded and decoded. Under the covers of the Image
’s source is a constructed object called a BitmapImage
. The BitmapImage
has a property called CreationOptions
in which you can specify when an image is loaded. By default, the CreationOptions
specify that images should be delay-loaded.
This means images are not loaded until they can be seen on the surface
of a page. In addition to delay-loading the image, you can also specify
that an image is loaded on the background thread. Specifying these two
things can improve the overall performance of images in a collection. To
specify this you would need to break out the Image.Source
property and set a BitmapImage
in the control template, like this:
<ListBox ItemsSource="{StaticResource theData}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding ImageUrl}"
CreateOptions="DelayCreation,BackgroundCreation" />
</Image.Source>
</Image>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can see in this example that the Image
is using the verbose XAML syntax to set the Source
to a BitmapImage
object. The UriSource
takes the same binding the earlier example used for the Source
. Finally, the CreateOptions
is set to both DelayCreation
and BackgroundCreation
to improve the performance of loading this image in the user interface.
Additionally, you can improve the performance of scrolling by allowing the object responsible for scrolling in lists (the ScrollViewer
class) to decide whether scrolling should be handled by the operating
system (the default) or by the control. Depending on the size of the
scrolling region, giving scrolling responsibility to the control can
improve the user’s experience. To change this, you can specify the ScrollViewer.ManipulationMode
attached property on any list control (e.g., ListBox
, ItemsControl
, ScrollViewer
, etc.) to the value of Control
, as shown here:
<ListBox ItemsSource="{StaticResource theData}"
ScrollViewer.ManipulationMode="Control">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding ImageUrl}"
CreateOptions="DelayCreation,BackgroundCreation" />
</Image.Source>
</Image>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
4. Binding Formatting
During the data binding process, you have the opportunity to format
the data directly in the binding. Several binding properties allow you
to specify what happens during binding. These include StringFormat
, FallbackValue
, and TargetNullValue
.
Each can impact what the user sees during data binding. For example,
you can include these as additional properties inside the binding, as
shown here:
<TextBox Text="{Binding ReleaseDate,
StringFormat=d,
FallbackValue='n/a',
TargetNullValue='n/a'}" />
The StringFormat
property is used to specify a .NET
format string to be used during binding. This can be any .NET format
string that matches the type. If the .NET format string contains spaces,
you should surround it with single quotes. The StringFormat
is used both to format the data when pushing it to the control as well as to parse the data going back to the source.
The FallbackValue
is used to show a value when the
binding fails. A binding can fail if it does not find a source (e.g.,
source or data context is null) or when the source does not have a valid
property to bind to.
Finally, the TargetNullValue
is used to indicate that the binding succeeded, but the value of the bound result is null.
Element Binding
You can also create bindings that allow you to create a link between two XAML elements. This is called element binding. To use element binding, you can specify the ElementName
as part of the binding syntax, like so:
<Slider Minimum="10"
Maximum="36"
x:Name="fontSizeSlider" />
<TextBox FontSize="{Binding Value, ElementName=fontSizeSlider}"
Text="Make It Grow" />
The size of the font in the TextBox
is being set based on the value of the Slider
(named fontSizeSlider
).
In this way, you can use elements in the XAML to supply data to other
elements in the XAML. A more conventional use of element binding is to
set the data context of a container based on the selected value of a
control, like so:
<ListBox ItemsSource="{Binding Games}"
x:Name="theList" />
<StackPanel DataContext="{Binding SelectedItem, ElementName=theList}">
<TextBlock>Name</TextBlock>
<TextBox Text="{Binding Name}" />
<TextBlock>Phone Number</TextBlock>
<TextBox Text="{Binding Phone}" />
</StackPanel>
In this example, the StackPanel
is setting its DataContext
to whatever item is selected in the ListBox
. In this way, you can show and/or edit the data in the StackPanel
based on the selection of the ListBox
.
5. Converters
Data binding takes properties from objects and moves them into
properties on controls. At times, the types of the properties will not
match or will need some level of manipulation to work. That is where
converters come in. Converters are stateless classes
that can perform specific conversions during the binding process. In
order to be a converter, a class must implement the IValueConverter
interface. This interface has two methods, Convert
and ConvertBack
,
to allow for conversions in both directions during binding. For
example, a simple converter to make dates show up as short date strings
looks like so:
public class DateConverter : IValueConverter
{
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
if (targetType == typeof(string) &&
value.GetType() == typeof(DateTime))
{
return ((DateTime)value).ToShortDateString();
}
// No Conversion
return value;
}
public object ConvertBack(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
if (targetType == typeof(DateTime) &&
value.GetType() == typeof(string))
{
DateTime newDate;
if (DateTime.TryParse((string)value, out newDate))
{
return newDate;
}
}
// No Conversion
return value;
}
}
Converters are created as resources (usually at the application level) like so:
<Application x:Class="PhoneControls.App"
xmlns="..."
xmlns:x="..."
xmlns:phone="..."
xmlns:shell="..."
xmlns:my="clr-namespace:PhoneControls">
<Application.Resources>
<my:DateConverter x:Key="dateConverter" />
</Application.Resources>
...
</Application>
By creating the converter at the application level, you can use it
throughout the application. Finally, we can now use the converter
directly in our data binding, like so:
<StackPanel DataContext="{Binding SelectedItem, ElementName=theList}">
<TextBlock>Name</TextBlock>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
<TextBlock>Phone Number</TextBlock>
<TextBox Text="{Binding PhoneNumber, Mode=TwoWay}" />
<TextBlock>Phone Number</TextBlock>
<TextBox Text="{Binding ReleaseDate,
Mode=TwoWay,
Converter={StaticResource dateConverter}}" />
</StackPanel>
During the conversion of the underlying data (in this case a DateTime
), the DateConverter
class is used. When moving the data from the source to the control, Convert
is called; when the data is pushed back to the source, the ConvertBack
method is called. As in this example, converters are often used just for formatting and not real conversion.