次の方法で共有


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.

wpfformat1

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.

wpfformat2

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,-B

  • Anonymous
    December 15, 2008
    Hi John,Good to hear from you! Thanks for the feedback.-B

  • Anonymous
    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/-B

  • Anonymous
    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,-B

  • Anonymous
    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,-B

  • Anonymous
    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 entry

  • Anonymous
    January 06, 2009
    Today we released a new How Do I Video onto the VB Dev Center on how to format controls on data entry

  • Anonymous
    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.aspx

  • Anonymous
    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,-B

  • Anonymous
    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).ThanksDominique

  • Anonymous
    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,-B

  • Anonymous
    March 29, 2010
    The comment has been removed