Dec 15, 2011

Displaying and Editing Percentages in WPF

Showing percentages isn't even worth a blog post (spoiler alert StringFormat=P) but modifying the value proved to be more work than it seems it should be. If you show a percentage in a TextBox you want code similar to the following:

<TextBox Text="{Binding CostMarkup,
                        StringFormat=P,
                        Converter={StaticResource pctConverter}}"/>

imageIf you have a backing value for CostMarkup of say 0.25, it will show as 25% as shown to the left. Unfortunately, if you go in and edit the value to say 30 %, first you'll get some exceptions (depending on your Debug->Exception settings) and at best you'll end up with a red box indicating a validation error and at worst you'll have accidentally changed the markup to 3,000% (well in the case of a Markup that's kind of  a happy accident if you can actually sell anything).  If you try this same experiment with a field formatted for money (i.e. StringFormat=C) this doesn't happen. Obviously we avoid the multiply by 100 problem since we don't store currency in a different order of magnitude than how we display it, but that $ and all those commas don't cause us a problem either. That's really slick and it's unfortunate that the percent format doesn't do the same thing. I suspect the built in currency converter is helping out behind the scenes here.

Luckily we can manually reproduce the behavior of currency by writing our converter. Writing your first converter probably seems like a lot of work but it's not that hard and once you have one in a project, it's pretty boiler plate to write additional ones. I start by creating a new Folder named Converters under my ViewModel folder. Then since I'm a big fan of small classes I create a PercentageConverter class in its own file, that implements the IValueConverter interface.

Percentage Convertor Class
  1. using System;
  2. using System.Windows.Data;
  3.  
  4. namespace HssOrderTracker.ViewModel.Converters
  5. {
  6.     public class PercentageConverter : IValueConverter
  7.     {
  8.         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  9.         {
  10.             return value;
  11.         }
  12.  
  13.         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  14.         {
  15.             string value_str = value.ToString();
  16.             if (String.IsNullOrWhiteSpace(value_str)) return null;
  17.  
  18.             value_str = value_str.TrimEnd(culture.NumberFormat.PercentSymbol.ToCharArray());
  19.            
  20.             double result;
  21.             if(Double.TryParse(value_str, out result))
  22.             {
  23.                 return result/100.0;
  24.             }
  25.             return value;
  26.         }
  27.     }
  28. }

Since I'm going to use percentage fields in various places in my application, I modify my SharedResources.xaml file which holds my shared resource dictionary. At the top of my resource dictionary I add

xmlns:Converters="clr-namespace:HssOrderTracker.ViewModel.Converters"

Then I can easily add a reference for any converter I write. For the current percent converter it looks like:

<Converters:PercentageConverter x:Key="pctConverter" />

Once all this in place, the actual XAML is a piece of cake to modify. I just add a reference to the Convertor in the binding. The final XAML becomes:

XAML with convertor specified
  1. <TextBox Text="{Binding CostMarkup,
  2.                         StringFormat=P,
  3.                         Converter={StaticResource pctConverter}}" />

Now when I edit the field, the % symbol can stay there and I can enter the value as a percentage, changing the 25 to a 30 and the backing field gets set to .3, the screen shows 30% and all is well.

For completeness sake, I'll mention that I modify my app.xaml file to make my resource dictionary available as shown below:

Sharing the Dictionary
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="View\Reusable\SharedResources.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>

</Application.Resources>

If you would prefer to edit your percentages in their native format (i.e. .25), use only XAML and forgo writing an IValueConverter check out my second post on this topic: Editing Percentages in XAML Part II.

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.