Formatting Data in WPF Controls
One of the basic things that we need to do as business application developers is to make sure that data being displayed to a user makes sense to them and that they know what type of data something is when they are making edits. In every business application I've written the data entry forms needed to format data in different ways. Let's take a look at how we do this in Windows Presentation Foundation.
IValueConverter
In WPF there is an interface called IValueConverter that you can implement on a class to specify how data should be converted for display in the control as well as a way to convert it back to the data source. Then you reference this class in the XAML on the data binding for each control. For instance, let's say we have a very simple form displaying an order where we want to format the Order Date and Order Total.
We don't want to display the time portion of our date and we'd also like to display the decimal Total in currency format. We also want to make sure users can't type the wrong data types in those fields, i.e. don't allow character strings. The first thing to do is write a simple converter class that will format our controls for display as well as verify the correct data types are entered by the user.
Public Class MyFormatter
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
If parameter IsNot Nothing Then
Return Format(value, parameter.ToString())
End If
Return value
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
If targetType Is GetType(Date) OrElse targetType Is GetType(Nullable(Of Date)) Then
If IsDate(value) Then
Return CDate(value)
ElseIf value.ToString() = "" Then
Return Nothing
Else
Return Now() 'invalid type was entered so just give a default.
End If
ElseIf targetType Is GetType(Decimal) Then
If IsNumeric(value) Then
Return CDec(value)
Else
Return 0
End If
End If
Return value
End Function
End Class
The Convert method is called when data is coming out of the data source and into the control for display. Here we're taking advantage of the parameter called parameter (very creative name <g>). I'm just using the Visual Basic Format function here for simplicity but you can write any type of formatting code here. The VB Format function takes in simple string format styles to format values and there are a bunch of predefined ones for dates and numbers. You can also specify your own. It's very similar to the String.Format .NET framework method but it is simpler to specify the styles. So to format a date value to only show the date portion we could pass a 'd' as the parameter. Using the predefined masks the Format function is internationally aware. However, also note that you can use the culture parameter to determine the CultureInfo as well.
The ConvertBack method is called when data is going back into the data source and it gives us a chance to take the string values the user enters and convert them to their correct data types. Here I'm checking the targetType in order to determine what the type of the value should be and then attempting to convert it back to the real data type. If the value cannot be converted, say the user typed the character "B" for the date field, then I'm just returning a default value. In the case of my date it allows null values so if the user blanks out the date then the value becomes the empty string and we return Nothing (null).
Specifying the Converter on the Bindings
Next we need to augment the XAML so that we can refer to our converter class in the bindings. What we need to do is add this class as a static resource of the window by first adding an XML namespace to identify our local project and then adding the reference into the Window.Resources:
<Window x:Class="Formatting"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:MyProject"
Title="Formatting" Height="207" Width="300">
< Window.Resources ><br> <local:MyFormatter x:Key ="myformatter" /><br> </Window.Resources>
Now we can augment the data bindings of the fields we want formatting on by specifying the Converter and ConverterParameter properties on the Binding:
<TextBox Height="28" Width="Auto" Margin="3" IsReadOnly="True"
Name="txtID"
Text="{Binding Path=OrderId}"/>
<TextBox Height="28" Width="Auto" Margin="3"
Name="txtProductName"
Text="{Binding Path=ProductName}"/>
<TextBox Height="28" Width="Auto" Margin="3"
Name="txtOrderDate"
Text="{Binding Path=OrderDate , Converter={StaticResource myformatter}, ConverterParameter ='d' }"/>
<TextBox Height="28" Width="Auto" Margin="3"
Name="txtOrderTotal"
Text="{Binding Path=Total , Converter={StaticResource myformatter}, ConverterParameter ='c2'}" />
Now when we run our form we will see the data displayed properly and you'll also notice that invalid characters that would cause an incompatible data type aren't allowed. We can also be lazy with how we enter dates, for instance if we type '3-1' then it will be converted to 3/1/2008 (current year). This is generally a nice thing to enable for users.
Using String.Format instead of Format
If we don't want to (or can't) use the Format method then we can use the String.Format method instead to display our data by changing our Convert method code (notice that I have to explicitly specify the culture now):
If parameter IsNot Nothing Then
Return String.Format(culture, parameter.ToString(), value)
End If
However, the style parameter also needs to be changed as well because String.Format requires the styles to be surrounded by curly braces. This doesn't play so nice with the curly braces in the XAML binding so we end up having to escape them:
<TextBox Height="28" Width="Auto" Margin="3"
Name="txtOrderDate"
Text="{Binding Path=OrderDate, Converter={StaticResource myformatter},
ConverterParameter='\{0:d\ }' }"/>
<TextBox Height="28" Width="Auto" Margin="3"
Name="txtOrderTotal"
Text="{Binding Path=Total, Converter={StaticResource myformatter},
ConverterParameter='\{0:c\ }'} "/>
Now you know why I like Format better that String.Format :-)
So as you can see it's pretty easy to set up formatting on controls in WPF. Look for the next WPF How Do I video on this coming to a Developer Center near you very soon. :-)
Enjoy!
Comments
Anonymous
December 12, 2008
Beth, how about using Binding.StringFormat feature (.NET 3.5 SP1)? Does it provide the convert-back mechanism?Anonymous
December 13, 2008
Hey Beth,Good post and I'm also married to the Format function. You make a good point on enabling "lazy" users. It also applies to careless users. I'm a firm believer in restrictive data entry.Anonymous
December 15, 2008
Hi Maximillian,Using Binding.StringFormat is great for view-only data but you are correct, it doesn't provide the full featured type conversion and defaults when users enter data. So for data entry it's better to support a converter like I showed above. However Binding.StringFormat (and the ContentStringFormat for content controls, HeaderStringFormat, ItemStringFormat, etc.) are easy for controlling the display of data.Cheers,-BAnonymous
December 15, 2008
Hi John,Good to hear from you! Thanks for the feedback.-BAnonymous
December 15, 2008
Beth,The documentation for IValueConverter shows the class decorated with the ValueConversion attribute. The only indication I can find for why to use this attribute is "to indicate to development tools the data types involved in the conversion". Can you elaborate on this? Do you know of any specific examples where using this attribute buys functionality that you don't get without it?Thanks.Anonymous
December 16, 2008
Hi Allan,I don't think it is used currently by the WPF designer but it will be in the future. It's used to indicate to designers which converters would apply to the bound data types on the controls.I'd ask the question in the WPF forum: http://social.msdn.microsoft.com/forums/en-US/wpf/threads/-BAnonymous
December 16, 2008
I thought freedom of speech was ok. it seems to me that only positive blog posts will stay here. Be warned everyone, Beth is a communist.Anonymous
December 16, 2008
hi Rich,Please stop spamming my blog and I wont have to do that. Your posts are completely off topic. I spoke with Rich at Strangeloop and you are not him so quit it.Thanks,-BAnonymous
December 17, 2008
How about if we wanted to keep the order date having date and time?thanks.Anonymous
December 18, 2008
Hi Carlos,Take a look at the predefined date formats that you can specify to the Format function here: http://msdn.microsoft.com/en-us/library/362btx8f.aspxFor instance instead of specifying a 'd' in the XAML you can supply a 'g' as the ConverterParameter and the short date and time will display.Cheers,-BAnonymous
December 23, 2008
Hi Beth,thanks for the clarification. I think the StringFormat name should be changed to DisplayStringFormat since it was confusing to me at first.Anyway, I'll try your way using IValueConverter then.Thanks!Anonymous
December 23, 2008
Hi Beth, thanks for the clarification. I think Binding.StringFormat should be renamed to Binding.DisplayStringFormat since it was confusing for me at first. I hardly found the emphasis on "view-only data" matter on the MSDN docs since Binding.StringFormat can also be used on non-readonly TextBox. So, I think the docs should be improved. Finally, I'll try your suggestion using IValueConverter as it's much more flexible IMO. Thanks!Anonymous
January 06, 2009
We just released a new How Do I Video onto the VB Dev Center on how to format controls on data entryAnonymous
January 06, 2009
Today we released a new How Do I Video onto the VB Dev Center on how to format controls on data entryAnonymous
January 06, 2009
Great post, Beth, thanks!For anyone interested, I just put up a somewhat related post on using IValueConverter to translate numeric values between different scales - this one is also in VB by the way:http://www.perceptible.net/post/2009/01/06/Translating-Numeric-Values-Between-Different-Scales-in-WPF-Data-Binding.aspxAnonymous
January 12, 2009
Hi Beth,I am very beginner. for novice like me, could u please upload the sample project of the 'Formatting Data in WPF Controls' ?Thanks Beth.Anonymous
January 13, 2009
Hi sohan,The video and sample code can be downloaded here: http://msdn.microsoft.com/en-us/vbasic/dd367843.aspxCheers,-BAnonymous
January 23, 2009
Thank you for the good information.I could get from yours.Anonymous
February 11, 2009
Hi Beth,IValueConverter works with WPF.How can I do the same in WinForms ? (the same is binding with convert).ThanksDominiqueAnonymous
July 07, 2009
Hi Dominique,Take a look at the Binding class' Parse and Format events.http://msdn.microsoft.com/en-us/library/system.windows.forms.binding.parse.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.forms.binding.format.aspxHTH,-BAnonymous
March 29, 2010
The comment has been removed