Perhaps as you were playing
around with the SliderBindings program (or as you gaped in amazement at
that screenshot), you were started to see that the TextBlockSlider value
sometimes as an integer, sometimes with one or two decimal points, but
mostly in the full 15-digit glory of double-precision floating point. displays the
Is there a way to fix that?
Yes there is. One of the
properties of the Binding class is Converter, and
the purpose of this property is to reference a class that converts data on its way from
the source to the target and (if necessary) back the other way.
Obviously, some implicit data conversion is being performed regardless as numbers
are converted to strings and strings converted to numbers. But we can
provide a little more explicit assistance to this conversion.
The Converter property of the Binding class is of type IValueConverter, an
interface that requires only two methods named Convert
and ConvertBack. Convert handles the data conversion from the
source to the target, and ConvertBack handles the conversion going in the other
direction for a TwoWay binding.
If you never intend to use
the conversion class with two-way
bindings, you can simply return null
from ConvertBack.
To add a simple converter
to SliderBindings, add a new class to the project and call it TruncationConverter.
Actually the class is already in the project, and here it is:
Example 1. Silverlight
Project: SliderBindings File: TruncationConverter.cs
using System; using System.Globalization; using System.Windows.Data;
namespace SliderBindings { public class TruncationConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is double) return Math.Round((double)value);
return value; }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value; } } }
|
The value argument to
the Convert method
is the object passing from the source to the target. This method just
checks if it’s a double.
If so, it explicitly casts it to a double
for the Math.Round method.
You’ll need to reference
this class in MainPage.xaml, which means you’ll need an XML namespace
declaration:
xmlns:local="clr-namespace:SliderBindings"
The TruncationConverter class is then made a resource:
<phone:PhoneApplicationPage.Resources>
<local:TruncationConverter x:Key="truncate" />
. . .
</phone:PhoneApplicationPage.Resources>
You’ll find these additions
already in the MainPage.xaml file of the SliderBindings project.
The Binding markup extension then references this
resource:
<TextBlock Name="txtblk"
Text="{Binding ElementName=slider,
Path=Value,
Converter={StaticResource truncate}}" . . . />
I’ve split the markup
extension into three lines so the components are clearly visible. Notice
that the StaticResource is another markup extension embedded in the
first markup extension so the entire expression concludes with a pair of
curly braces.
And now the number
displayed by the TextBlock is
truncated:
Be sure to reference the converter
as a StaticResource.
It is often very tempting to just set the Converter
property of Binding to the key name:
<!-- This is wrong! -->
<TextBlock Name="txtblk"
Text="{Binding ElementName=slider,
Path=Value,
Converter=truncate}" . . . />
I still do that myself
very often, and tracking down the problem can be difficult.
Defining the converter as a
resource is certainly the most common approach to reference converters, but it’s not
the only way. If you use the element syntax of Binding, you can embed the TrunctionConverter class directly into the
markup:
<TextBlock . . . >
<TextBlock.Text>
<Binding ElementName="slider"
Path="Value">
<Binding.Converter>
<local:TruncationConverter />
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
However, if you have multiple
references in the XAML file to this same converter, defining it as a
resource is preferable because it allows the single instance to be
shared.
TrucationConverter is actually a terrible
data converter.
Sure it does what it’s supposed to do but it doesn’t do it in a very
versatile manner. If you’re going to be calling Math.Round in a converter
class, wouldn’t it be better to have the option of rounding to a
certain number of decimal places? Come to think of it, wouldn’t it make
more sense to allow all different kinds of formatting—not just of
numbers but of other data types as well?
That magic is provided by a class
in the Petzold.Phone.Silverlight library called StringFormatConverter:
Example 2. Silverlight
Project: Petzold.Phone.Silverlight File: StringFormatConverter.cs
using System; using System.Globalization; using System.Windows.Data;
namespace Petzold.Phone.Silverlight { public class StringFormatConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (targetType == typeof(string) && parameter is string) return String.Format(parameter as string, value);
return value; }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value; } } }
|
Besides a Converter property, the Binding class also has a ConverterParameter
property. The value of that property enters the Convert call as the parameter
argument. The Convert method here
assumes that parameter
argument as a standard .NET formatting string that can be used in the String.Format call.
To use this
converter in the SliderBindings program, you’ll need a reference to the
Petzold.Phone.Silverlight library. (That’s already been done.) Already
added to the file as well is an XML namespace declaration:
xmlns:petzold="clr-namespace:Petzold.Phone.Silverlight;assembly=Petzold.Phone.Silverlight"
Instantiate the StringFormatConverter
in the Resources collection of the
page:
<phone:PhoneApplicationPage.Resources>
. . .
<petzold:StringFormatConverter x:Key="stringFormat" />
</phone:PhoneApplicationPage.Resources>
You can now reference
that converter in the Binding markup
expression. Set the ConverterParameter to a .NET formatting string with one placeholder:
Text="{Binding ElementName=slider,
Path=Value,
Converter={StaticResource stringFormat},
ConverterParameter=. . .}"
And as you start to
type a .NET formatting string, you realize there’s a problem. The
standard .NET
formatting strings involve the use of curly braces, and you’re pretty
sure that when the XAML parser attempts to decode a Binding markup
expression, it’s not going to appreciate unauthorized embedded curly
braces.
The simple solution is to enclose
the value of the ConverterParameter in
single quotes:
Text="{Binding ElementName=slider,
Path=Value,
Converter={StaticResource stringFormat},
ConverterParameter='{0:F2}'}"
The XAML parser and
visual designer in Visual Studio doesn’t care for this particular syntax
either, but it’s not a problem at runtime. If you want the designer to
accept this, insert a space (or another character) after the first single quotation mark.
Because you know that the ConverterParameter becomes the first argument
to a String.Format call, you can
spruce it up a bit:
Text="{Binding ElementName=slider,
Path=Value,
Converter={StaticResource stringFormat},
ConverterParameter='The slider is {0:F2}'}"
And here’s the result: