Implementing String Formatters and Value Converters
The need to represent
strings in a more appropriate format when binding data to the user
interface is not uncommon. For example, you might want to present money
values or percentages. In WPF you can accomplish this in two modes:
string formatters and the IValueConverter interface. This section describes both, showing how they can be used for better presentation purposes.
Implementing String Formatters
Starting from .NET 3.5 SP 1, you can apply string formats directly in the XAML Binding
markup extension that performs data-binding. This allows expressing a
particular value type in a more convenient string format. For a better
understanding, consider Figure 6 Notice now the Freight
field is shown as a decimal number but probably you might want to
display it with your currency symbol. Locate the XAML code that
implements the Freight textbox and apply the StringFormat property as shown in the following code snippet:
<TextBox Grid.Column="1" Grid.Row="7" Height="23"
HorizontalAlignment="Left" Margin="3"
Name="FreightTextBox"
Text="{Binding Path=Freight, StringFormat=c}"
VerticalAlignment="Center" Width="120" />
The BindingBase.StringFormat property is applied within the Binding markup extension and requires the specification of the formatter. Figure 7 shows how the Freight field is now represented with a currency symbol.
Table 3 summarizes the most common StringFormat values.
Table 3. Most Common Formatters Values
Value | Description |
---|
c or C | Represents a value as a string with currency symbol. |
p or P | Formats a value as a string with percentage representation. |
D | Formats a date value as an extended string representation (for example, Monday, 21 October 2009). |
D | Formats a date value as a short string representation (for example, 10/21/2009). |
F | Provides
a string representation of a decimal number with floating point. It is
followed by a number that establishes how many numbers follow the
floating point (for example, 3.14 can be represented by F2). |
E | Scientific formatting. |
X | Hexadecimal formatting. |
G | General. |
The good news is that string formatters also provide converting back the user input. For the Freight
example, if you type a value into the field, it is represented as a
currency in the user interface, but it is correctly saved to the data
source according to the required type. StringFormat also enables string formatting as it happens in Visual Basic code. Consider the following code:
<TextBox Name="FreightTextBox"
Text="{Binding Path=Freight, StringFormat=Amount: {0:c}}"/>
In the preceding code the Amount word takes the place of 0 at runtime. So the result will be Amount: $ 1.21. There is another useful technique known as MultiBinding. The following code demonstrates how it is possible to apply multiple formatters with MultiBinding:
<!—Applies date and currency formatting—>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="Order date: {0:D}, Cost: {1:C}">
<Binding Path="OrderDate"/>
<Binding Path="OrderPrice"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Several user controls, such as Button and Label, also offer a ContentStringFormat property that enables applying formatting to the control’s content the same way as StringFormat works. The following is an example:
<Label ContentStringFormat="C" Content="200"/>
Similarly, controls such as ListView, ListBox, and DataGrid offer the HeaderStringFormat and ItemStringFormat
properties that enable, respectively, formatting the header content for
columns and items in the list. String formatters are straightforward,
but there are situations in which you need more extensive control over
value representation, especially when you need to actually convert from
one data type to another. This is where IValueConverter comes in.
Implementing the IValueConverter Interface
There are situations in which
default conversions provided by string formatters are not enough,
especially if you have to implement your custom logic when converting
from the user input into another type. With IValueConverter
you can implement your custom logic getting control over the conversion
process from and to the data source. To follow the next steps, create a
new class and name it CustomConverter. When the new class is ready, implement the IValueConverter interface. The resulting code will be the following:
Public Class CustomConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object,
ByVal targetType As System.Type,
ByVal parameter As Object,
ByVal culture As
System.Globalization.CultureInfo) _
As Object Implements _
System.Windows.Data.IValueConverter.Convert
End Function
Public Function ConvertBack(ByVal value As Object,
ByVal targetType As System.Type,
ByVal parameter As Object,
ByVal culture As System.Globalization.
CultureInfo) As Object _
Implements _
System.Windows.Data.IValueConverter.
ConvertBack
End Function
End Class
The interface implementation requires two methods, Convert and ConvertBack.
The first one manages data when applying from the data source to the
user interface, whereas the second one manages the conversion when
getting back from the user interface to the data source. The most
important argument in the Convert method is parameter, which represents how data must be converted. Such data is stored by the value argument. Implementing Convert is quite easy, in that generally you simply need to format value as a string according to parameter’s establishment, and this can be accomplished taking advantage of the current culture. The following is the standard Convert implementation:
Public Function Convert(ByVal value As Object,
ByVal targetType As System.Type,
ByVal parameter As Object,
ByVal culture As _
System.Globalization.CultureInfo) _
As Object Implements System.Windows.Data.
IValueConverter.Convert
If parameter IsNot Nothing Then
Return String.Format(culture, parameter.ToString, value)
End If
Return value
End Function
Basically it ensures that on the XAML side a valid converter property (reflected by parameter),
which is described later, has been provided and that it is not null. In
this case the method returns the string representation of the value
according to the system culture. If no converter is specified, the
method simply returns the value. ConvertBack
is a little bit more complex, because it has to convert strings (that
is, the user input) into a more appropriate type. The goal of this
example is providing conversion from String to Decimal, for money fields. The following code snippet implements the method (see comments for explanations):
Public Function ConvertBack(ByVal value As Object,
ByVal targetType As System.Type,
ByVal parameter As Object,
ByVal culture As System.Globalization.
CultureInfo) As Object _
Implements System.Windows.Data.
IValueConverter.ConvertBack
'If the type to send back to the source is Decimal or Decimal?
If targetType Is GetType(Decimal) OrElse targetType _
Is GetType(Nullable(Of Decimal)) Then
Dim resultMoney As Decimal = Nothing
'Checks if the input is not null
If Decimal.TryParse(CStr(value), resultMoney) = True Then
'in such case, it is returned
Return CDec(value)
'if it is empty, returns Nothing
ElseIf value.ToString = String.Empty Then
Return Nothing
Else
'If it is not empty but invalid,
'returns a default value
Return 0D
End If
End If
Return value
End Function
It is worth mentioning that
you need to provide conversion for nullable types, as in the preceding
code, if you work against an Entity Data Model. If your user interface
simply presents data, but does not receive input from the user, you can
implement ConvertBack by simply putting a Throw New NotImplementedException
as the method body. The MSDN official documentation suggests an
interesting best practice when implementing custom converters. This
requires applying the ValueConversion
attributes to the class; this attribute enables specifying data types
involved in the conversion, as in the following line that has to be
applied to the CustomConverter class:
<ValueConversion(GetType(String), GetType(Decimal))>
The first attribute’s argument
is the type that you need to convert from, whereas the second one is
the type that you need to convert to. Custom converters must be applied
at XAML level. This requires first adding an xml namespace pointing to
the current assembly that defines the class. For the previous example,
add the following namespace declaration within the Window element definition, taking care to replace the IntroducingDataBinding name with the name of your assembly (IntelliSense will help you choose):
xmlns:local="clr-namespace:IntroducingDataBinding"
When you have a reference
to the assembly, which can be useful for utilizing other classes at the
XAML level, you need to declare a new resource that points to the custom
converter. Within the Window.Resources element, add the following line:
<local:CustomConverter x:Key="customConverter"/>
Now that the converter
has an identifier and can be used at the XAML level, you simply pass it
to the bound property you want to convert. For example, suppose you want
to format and convert the Freight property from the Order class. The following code demonstrates how to apply the converter:
<TextBox Grid.Column="1" Grid.Row="7" Height="23"
HorizontalAlignment="Left" Margin="3"
Name="FreightTextBox"
Text="{Binding Path=Freight,
Converter={StaticResource customConverter},
ConverterParameter='\{0:c\}'}"
VerticalAlignment="Center" Width="120" />
You pass the converter identifier to the Converter property of the Binding markup extension. The ConverterParameter receives the conversion value, which are the same in Table 3. If you run the application you get the result shown in Figure 7, the difference is that with custom converters you can control how the conversion and formatting processes behave.