How do I format Numbers and Dates when using WPF Data Binding?

It is often desirable to control the format of dates, times, money and percentages when they are displayed. The example screen-shot below shows a list of 3 sale records. The sale information includes an Id, the sale amount, the percentage of sales tax paid and the time of the sale. Although the information displayed to the user is correct, the unformatted data might be difficult or confusing for a user to read.

unformatted dates, currency values and percentages

The .NET framework includes a small formatting mini-language using strings like "{0:C}" to format a parameter as currency. WPF data binding does not have any facility built-in to allow this formatting, however it is easy to add by extending the built-in binding infrastructure. This technique I originally came across here in the WPF forums, but it has been extended here to allow formatting of all data types.

Implementing a custom ValueConverter for Formatting
To extend the binding infrastructure to allow formatting I've created a ValueConverter to convert between a .NET object and a string. Value converters implement the IValueConverter interface in the System.Windows.Data namespace. This interface has two methods, Convert and ConvertBack for converting between two types. To convert the data from a .NET object to a formatted string we're only going to implement the Convert() method. The formatting string (like "{0:MM-dd-yyyy}" if we wished to format a date) is passed to the Convert() method of the ValueConverter via the aptly named parameter parameter. Fortunately the Convert() method is also passed a CultureInfo parameter which we can use to perform our formatting in a culture-specific way. Formatting should be culture aware, as different countries use different symbols for currency, have different date time formats and different symbols to separate the whole and fractional parts of non-integer real numbers.  The code for the ValueConverter is shown below:

C# Code (Feb 2006 CTP)

using System;
using System.Windows.Data;

namespace LearnWPF.FormattingAndDataBinding
{
    [ValueConversion(typeof(object),typeof(string))]
    public class FormattingConverter: IValueConverter
    {
        public object Convert(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            string formatString = parameter as string;
            if (formatString != null)
            {
                return string.Format(culture, formatString, value);
            }
            else
            {
                return value.ToString();
            }
        }

        public object ConvertBack(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            // we don't intend this to ever be called
            return null;
        }
    }
}

Using the Converter from our WPF Application
To use the converter in our WPF application requires 3 steps:

1 - Create an Xml namespace prefix for the CLR namespace that contains the ValueConverter. This is necessary any time you want to use one of your own types in mark-up. The example below tells the Xaml run-time that any time it sees a "my" prefix on an element it should look in the LearnWPF.FormattingAndDataBinding namespace in the current assembly. More details of this can be found here in the Windows SDK.

xmlns:my="clr-namespace:LearnWPF.FormattingAndDataBinding"

2 - Create an instance of our formatter as a resource*. In this sample I've chosen to place it in the resources dictionary for the window. A key is required so we can retrieve it later.

<my:FormattingConverter x:Key="formatter" />

3 - Use the Converter when binding. We do this by specifying a Converter for the binding (the converter we created in step 2 as a resource), and also setting the ConverterParameter. The ConverterParameter is passed to the IValuConverter's Convert() method as the (aptly named) parameter parameter. The example below passes a custom date format string {0:dd-MMM-yyy hh:mm}.

<TextBlock Text="{Binding Path=TimeOfSale,
  Converter={StaticResource formatter},
  ConverterParameter=' \{0:dd-MMM-yyyy hh:mm\}'
}" />

 
Note that in the format string the curly brackets are escaped using the backslash character like this \{ and this \}. This is necessary because the curly brackets are used in Xaml for specifying markup extensions like that {Binding} and {StaticResource} extensions seen in this example. We need to differentiate our formatting string from those. 
   
Here is the Xaml code containing all three steps

Xaml Code (Feb 2006 CTP)

<Window x:Class="LearnWPF.FormattingAndDataBinding.Window1"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation "
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml "
    xmlns:my="clr-namespace:LearnWPF.FormattingAndDataBinding"
    Title="LearnWPF.FormattingAndDataBinding" Height="300" Width="300"
    Loaded="windowLoaded"
    >
  <Window.Resources>
    <ObjectDataProvider x:Key="sales" />
    <my:FormattingConverter x:Key="formatter" />
  </Window.Resources>
  <ListBox DataContext="{StaticResource sales}" ItemsSource="{Binding}">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <Border BorderBrush="#feca00" BorderThickness="2"
                CornerRadius="3" Padding="5" Margin="2">
          <StackPanel Orientation="Horizontal">

            <!-- formatting the Id -->
            <TextBlock Text="{Binding Path=Id,
              Converter={StaticResource formatter},
              ConverterParameter='Sale No:\{0\} '}" />

            <!-- formatting the Amount -->
            <TextBlock Text="{Binding Path=Amount,
              Converter={StaticResource formatter},
              ConverterParameter=' \{0:C\}'}"
              FontWeight="Bold" />

            <!-- formatting the SalesTaxPercentage -->
            <TextBlock Text="{Binding Path=SalesTaxPercentage,
              Converter={StaticResource formatter},
              ConverterParameter=' (\{0:P\} Sales Tax)'}" />

            <!-- formatting the date -->
            <TextBlock Text="{Binding Path=TimeOfSale,
              Converter={StaticResource formatter},
              ConverterParameter=' \{0:dd-MMM-yyyy hh:mm\}'}" />

          </StackPanel>
        </Border> 
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</Window>

Here is the resulting window with nicely formatted data

Formatted dates, currency values and percentages

The formatting converter shown above only performs a one-way conversion - from the .NET object to a string. Converting back to the underlying data type from a string might be possible by inspecting the targetType passed as an argument to the ConvertBack() method, or you could write a type-specific converter with a tailored ConvertBack() implementation. Since the formatting converter only converts in one direction it is really only useful for formatting data for display in a read-only manner.

You can download the complete source code for this example here.

* Note: It is not absolutely necessary to create our Converter as a resource, since we could use the less-compact property element syntax for the binding and create an instance of the Converter each time we wanted to use it. Defining the converter as a resource and re-using it is recommended.