Friday, 2 March 2012

WPF String Formatting

There are a number of ways to use formatted strings and we seem to use nearly all of them in our application. Because the format of the formatting string varies slightly I thought I'd document the different techniques.
Firstly we need some simple properties for the XAML Bindings. Notice that the ToString version needs neither the index (0:) or the curly braces.
public partial class MainWindow : Window
{
   public String FormattedDate
   {
      get { return String.Format("{0:dd MMM yyy}", DateTime.Now); }
   }
   public String DateToString
   {
      get { return DateTime.Now.ToString("dd MMM yyy"); }
   }
   public DateTime RawDateTime
   {
      get { return DateTime.Now; }
   }
   public MainWindow()
   {
      InitializeComponent();
      DataContext = this;
   }
}
The first two TextBlocks bind directly to the pre-formatted properties.
      <TextBlock Text="{Binding FormattedDate}" />
      <TextBlock Text="{Binding DateToString}" />
Or we can bind to the raw data and use StringFormat. We can escape the curly braces in a couple of ways.
      <TextBlock Text="{Binding Path=RawDateTime, StringFormat=\{0:dd MMM yyyy\}}" />
      <TextBlock Text="{Binding Path=RawDateTime, StringFormat={}{0:dd MMM yyyy}}" />
The leading curly braces in the second version can be replaced by constant text that is rendered as part of the formatted string. But it seems a bit "sensitive" so I don't use it.
      <TextBlock Text="{Binding Path=RawDateTime, StringFormat=Today is {0:dd MMM yyyy}}" />
Next up is a Converter for which we need an IValueConverter class:
class DateConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      if (value is DateTime)
      {
         return ((DateTime)value).ToString("dd MMM yyy");
      }
      return value;
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {
      throw new NotImplementedException();
   }
}
And the corresponding XAML to use the Converter:
        xmlns:local="clr-namespace:WpfWaltz"
...
   <Window.Resources>
      <local:DateConverter x:Key="dateConverter"; />
   </Window.Resources>
...
      <TextBlock Text="{Binding Path=RawDateTime, Converter={StaticResource dateConverter}}" />
Finally, I had assumed that the indexer in the StringFormat was just a necessary evil since it was always 0: and hadn't considered using StringFormat with a MultiBinding like this:
      <TextBlock>
         <TextBlock.Text>
            <MultiBinding StringFormat="{}{0:dd MMM yyyy} {1:hh:mm:ss}">
               <Binding Path="RawDateTime" /> 
               <Binding Path="RawDateTime" />
            </MultiBinding>
         </TextBlock.Text>
      </TextBlock>
The StringFormatting project that has all the code can be downloaded from GoogleCode.

No comments: