Archives

Anticipation

  • No dates present

StringFormatConverter

Today’s topic is fairly basic, but hey, it gives me a chance to moan about something weird in the framework, so it’s not all bad :)

Windows Presentation Foundation. WPF. Essentially it’s a long-overdue reboot of the child window model coupled with a powerful data binding engine (though not without its own quirks). And I love it.

The data binding model, though, does tend to result in the proliferation of little helper classes. In this case, I’m referring to value converters, those classes built solely to take a property value, convert it for display purposes (usually to a string), and optionally back the other way again.

Many of these are fairly basic, and it irritates me to have so many little “throwaway” classes that are all so similar to each other. So here is a kind of “catch-all” converter class that I’ve built for general formatting. It won’t completely replace the need to make custom converters on occasion, of course, but it will replace a reasonable chunk of them:

[ValueConversion(typeof(object), typeof(string))]
public class StringFormatConverter : IValueConverter, IMultiValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return Convert(new object[] { value }, targetType, parameter, culture);
  }
 
  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    System.Diagnostics.Trace.TraceError("StringFormatConverter: does not support TwoWay or OneWayToSource bindings.");
    return DependencyProperty.UnsetValue;
  }
 
  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
  {
    try
    {
      string format = (parameter == null) ? null : parameter.ToString();
      if (String.IsNullOrEmpty(format))
      {
        System.Text.StringBuilder builder = new System.Text.StringBuilder();
        for (int index = 0; index < values.Length; ++index)
        {
          builder.Append("{" + index + "}");
        }
        format = builder.ToString();
      }
      return String.Format(/*culture,*/ format, values);
    }
    catch (Exception ex)
    {
      System.Diagnostics.Trace.TraceError("StringFormatConverter({0}): {1}", parameter, ex.Message);
      return DependencyProperty.UnsetValue;
    }
  }
 
  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
  {
    System.Diagnostics.Trace.TraceError("StringFormatConverter: does not support TwoWay or OneWayToSource bindings.");
    return null;
  }
}

The usage is fairly straightforward; simply add one to your window or application resources, and then use like so for a standard binding:

ToolTip="{Binding Path=Hostname,
    Converter={StaticResource StringFormatConverter},
    ConverterParameter=Connected to {0}}"

Or like this as a multi-binding:

<TextBlock>
  <TextBlock.Text>
    <Binding Converter="{StaticResource StringFormatConverter}"
             ConverterParameter="{}{0}: {1}">
      <Binding Path="When" />
      <Binding Path="Message" />
    </Binding>
  </TextBlock.Text>
</TextBlock>

(Note that you need to use an initial {} escape for the multi-binding if your format string begins with a replacement placeholder.)

Points worth mentioning:

  • Because this just uses String.Format under the covers, you can use format strings like {0:s} or {0:##0.00} if you like.
  • If you don’t specify a ConverterParameter, then the multi-bind converter will default to simply appending all of the values together (without any extra spacing). (The single-bind converter will just format what you gave it, which should be equivalent to not using the converter at all.)
  • Only one-way conversion is supported (though I guess it wouldn’t be impossible to implement a two-way binding, given certain constraints).
  • I’ve tried to make it fairly robust in the face of misuse; it’ll log an error to the trace (usually only visible in the IDE, though that can be configured) if it’s used improperly, or if something throws an exception. Either way it’ll use the previously-valid value for the binding.
  • Because Binding and MultiBinding are markup extensions, you (unfortunately) can’t bind any of their own properties. So you can’t directly calculate the ConverterParameter or get it from another property, although you can get it from a static resource. You can indirectly do it, though — you can give it a static resource object whose ToString returns the format you desire.

And that about wraps it up, except for one final point that I’d like to have a little bit of a rant about :)


You might have noticed that in the main Convert method I’ve commented out the bit that passes in the culture parameter. See, at first I thought this would all work similar to the TypeConverters, where the culture is passed down from the calling code, if explicitly specified, or left as null if not explicitly specified. It turns out that this is not the case for WPF, however.

In WPF, the culture parameter passed to the converter’s Convert method comes from the ConverterCulture attribute specified on the binding in the XAML, which is fair enough, and so far fairly analogous to the classic case.

Where things go horribly, horribly wrong, though, is that if there is no ConverterCulture specified against the binding in the XAML, then it defaults to the lang of the XAML document as a whole — and there are two really big problems with that:

  1. If not specified, the document language always defaults to “en-us“. This means in turn that the ConverterCulture will default to the US culture, which will greatly annoy those users not in the US (particularly those who don’t like US-format dates).
  2. The document language is always a concrete language; if you construct a Culture off this (as it will do behind the scenes) then you will get the defaults for that culture — in particular, you will not get any customisations that the user has made in the Control Panel, which will greatly annoy those users as well.

I’ve seen some code that tries to work around this problem by overriding the metadata on the language property, thereby forcing it to the root language of the current culture. Unfortunately, while this fixes the first problem, it still suffers from the second.

Only by using CultureInfo.CurrentCulture can you get the settings that the user has customised (and thus the ones that they prefer and are expecting). And conveniently, the standard behaviour of String.Format (and friends) when you pass in a null culture (or don’t pass it in at all) is to use the current culture settings. So that’s really the only sensible choice.

Long story short: don’t use the culture parameter in converters unless you are definitely explicitly specifying the ConverterCulture in the XAML and don’t mind using fixed settings instead of what the user would normally be expecting.

7 comments to StringFormatConverter


  • Catchable fatal error: Object of class WP_Comment could not be converted to string in /home/uecasm/public_html/lambert/wpdata/wp-content/themes/atahualpa/functions/bfa_custom_comments.php on line 16