Formatting Data in WPF Controls

Formatting Data in WPF Controls

  • Comments 20

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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyProject"    
    Title="Formatting" Height="207" Width="300">
    <Window.Resources>
        <local:MyFormatter x:Key="myformatter" />
    </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!

Leave a Comment
  • Please add 5 and 2 and type the answer here:
  • Post
  • Hi sohan,

    The video and sample code can be downloaded here: http://msdn.microsoft.com/en-us/vbasic/dd367843.aspx

    Cheers,

    -B

  • Thank you for the good information.

    I could get from yours.

  • Hi Beth,

    IValueConverter works with WPF.

    How can I do the same in WinForms ?  (the same is binding with convert).

    Thanks

    Dominique

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

    http://msdn.microsoft.com/en-us/library/system.windows.forms.binding.format.aspx

    HTH,

    -B

  • Code in C#:

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

    {

    if (!(parameter == null))

    {

    return string.Format(parameter.ToString(), value);

    }

    return value;

    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

    if (targetType == typeof(DateTime) || targetType == typeof(DateTime?))

    {

    if(value is DateTime)

    {

    return System.Convert.ToDateTime(value);

    }

    else if (value.ToString() == "")

    {

    return null;

    }

    else

    {

    return DateTime.Now; //invalid type was entered so just give a default.

    }

    }

    else if (targetType == typeof(Decimal))

    {

    {

    if (value is decimal)

    {

    return System.Convert.ToDecimal(value);

    }

    else

    {

    return 0;

    }

    }

    }

    return value;

    }

    }

Page 2 of 2 (20 items) 12