WPF Double to Fraction Converter

While on a project creating a WPF application for some real heavy Excel users, I’ve received a lot of requests to make it more like Excel.  One of the requirements that came in was to allow the users to type in fractions and view values as fractions in the WPF textboxes for values that were doubles in the model and in the database.

To fulfill this request, I needed a few things:

  • I needed to be able to find approximate fractional values for doubles.
  • I needed to validate and convert input in fractional format into a double.
  • I needed to output double values in a mixed fraction format.

Finding approximate fractional values for doubles

To handle this, I created a Fraction struct that has four properties.  IsPositive, WholeNumber, Numerator, and Denominator.  The meat of the struct is a static method called Parse that accepts a double and returns a Fraction.  It calls the private static method ApproximateFraction below:

/// <summary>
/// Approximates the provided value to a fraction.
/// </summary>
/// <param name="value">The double being approximated as a fraction.</param>
/// <param name="precision">Maximum difference targeted for the fraction to be considered equal to the value.</param>
/// <returns>Fraction struct representing the value.</returns>
private static Fraction ApproximateFraction(double value, double precision)
{
    bool positive = value > 0;
    int wholeNumber = 0; 
    int numerator = 1; 
    int denominator = 1;
    double fraction = numerator / denominator;

    while (System.Math.Abs(fraction - value) > precision)
    {
        if (fraction < value)
        {
            numerator++;
        }
        else
        {
            denominator++;
            numerator = (int)System.Math.Round(value * denominator);
        }

        fraction = numerator / (double)denominator;
    }

    if (numerator < 0) numerator = numerator * -1;

    while (numerator >= denominator)
    {
        wholeNumber += 1;
        numerator -= denominator;
    }

    return new Fraction(positive, wholeNumber, numerator, denominator);
}

Validate and convert fractional formatted inputs into doubles

This part actually ended up being the ConvertBack method in the IValueConverter created to bind to TextBoxes.  The basic idea here was to first try and parse the value as a double.  If it failed, I checked to see if it matched my regular expression for a mixed fraction.  If it did, I parsed that mixed fraction out and made a Fraction struct.  If not, I throw a FormatException.

/// <summary>
/// Converts a string into a nullable double
/// </summary>
/// <param name="value">A string value that should be transformable into a double</param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    if (value != null && !string.IsNullOrEmpty(value.ToString()))
    {
        double rValue;
        string rawValue = value.ToString().Trim();
        rawValue = rawValue.Replace("- ", "-");
        while(rawValue.Contains("  "))
        {
            rawValue = rawValue.Replace("  ", " ");
        }

        // Regular Expression that represents a number in Fraction format.
        Regex FractionRegex = new Regex(@"^-?([0-9]* )?[0-9]+/[0-9]+$");

        // If the value can be parsed as a double, do it and return
        if (double.TryParse(rawValue, out rValue))
        {
            return rValue;
        }
        // Else if the value can be read as a fractional value, extract the number and return the a double from it. 
        else if (FractionRegex.IsMatch(rawValue))
        {
            // Check to see if the input 
            if (FractionRegex.IsMatch(rawValue))
            {
                try
                {
                    Regex numeratorRegex = new Regex(@"(s|^|-)[0-9]+/");
                    bool isNegative;
                    int wholeNumber;
                    int numerator;
                    int denominator;

                    isNegative = rawValue.StartsWith("-");
                    wholeNumber = Math.Abs(rawValue.Any(x => x == ' ') ? int.Parse(rawValue.Remove(rawValue.IndexOf(" "))) : 0);
                    denominator = int.Parse(rawValue.Substring(rawValue.LastIndexOf("/") + 1));
                    numerator = Math.Abs(int.Parse((numeratorRegex.Match(rawValue)).Value.Replace("/", "")));

                    return new Fraction(!isNegative, wholeNumber, numerator, denominator).ToDouble();
                }
                catch
                {
                    throw new FormatException(String.Format("Invalid Format:  {0} cannot be converted to a numeric value.", value.ToString()));
                }
            }
        }
        //  This value could not be parsed as a double and didn't match a fraction using our Fractional Regular Expression, throw a FormatException.
        else
        {
            throw new FormatException(String.Format("Invalid Format:  {0} cannot be converted to a numeric value.", value.ToString()));
        }
    }

    return null;
}

Output double values in a mixed fraction format

An finally the Convert method of the IValueConverter used to display doubles bound to string properties such as TextBox.Text or TextBlock.Text.  In this method I have a couple of things going on, but first things first, I change the double into an instance of the Fraction struct.  Using the ConverterParameter, I allow the developer to choose whether or not they want to restrict the use of fractional output to certain denominators.  There are four possible return paths:

  1. Null, in the case the nullable double is null.
  2. Mixed fraction format when denominators are being restricted.
  3. The decimal format when denominators are being restricted and the fraction’s denominator isn’t in the allowed list.
  4. Mixed fraction format when denominators aren’t being restricted.
/// <summary>
/// Converts a double into a string.
/// </summary>
/// <param name="value">A nullable double to get converted into a string</param>
/// <param name="targetType"></param>
/// <param name="parameter">true or false, determines if we use the denominator restrictions</param>
/// <param name="culture"></param>
/// <returns>A string representing the double value, maybe in decimal format, maybe in fractional format.</returns>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    string decimalFormatString = "0.######";
    bool restrictDenominator = false;

    if (parameter != null)
    {
        if (!bool.TryParse(parameter.ToString(), out restrictDenominator))
        {
            restrictDenominator = false;
        }
    }

    double? dValue = value as Nullable<double>;
    if (restrictDenominator && dValue != null && dValue.HasValue)
    {
        Fraction asFraction = Fraction.Parse(dValue.Value);
        var validDenominators = new List<int>(new int[] { 2,3,4,5,6,7,8,9,16,32,64 });
        if (validDenominators.Contains(asFraction.Denominator))
        {
            return asFraction.ToString(validDenominators, decimalFormatString);
        }
        else
        {
            return dValue.Value.ToString(decimalFormatString);
        }
    }
    else if (!dValue.HasValue)
    {
        return dValue;
    }
    else
    {
        Fraction asFraction = Fraction.Parse(dValue.Value);
        return asFraction.ToString(null, String.Empty);
    }
}

Using the Converter in your WPF Application

The last thing of course would be to use the converter in your application.  If you haven’t used converters in WPF yet, it is fairly easy.  First you need to declare an XML namespace pointing at the .NET namespace where your converters live (see the line in the code sample below that starts “xmlns:converter”).  Next you need to declare a resource for your specific converter (See the one line in “Window.Resources”).  Finally you create a binding using the converter which is done below in the TextBox Text property.

<Window x:Class="Andora.BlogSample.FractionTextBoxes.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:converter="clr-namespace:Andora.BlogSample.FractionTextBoxes.Converters"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <converter:FractionConverter x:Key="fractionConverter" />
    </Window.Resources>
    <Grid Margin="15">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel>
            <Label Content="Enter a numeric in decimal or fraction format: " />
            <TextBox Text="{Binding MyDouble, Converter={StaticResource ResourceKey=fractionConverter}, ConverterParameter=true}" Height="25"  />

Conclusion

If you are developing in an environment where the users are accustomed to viewing and entering data in fraction formats, without too much hassle, you can provide users of your WPF applications the ability to do exactly that.  You can find a full working sample with code all zipped up using the link below.  Happy coding!!

Sample Project Download

WPF MarkupExtension Class

This week I was going through StackOverflow, looking for anything I could help with and cam across someone asking about how to bind to an attribute of a class member in XAML.  I wrote a small example IValueConverter class they could use to accomplish the task which was the only way I knew of that it could be done.  Then, another poster (H.B.) solved the task using something I’d never heard of before…the MarkupExtension class.

Using the example H.B. gave, creating a class to display the DisplayName attribute of a property, you can see that this approach gets the job done just as well as a converter, but to me, it looks like it solves the problem in a much more direct way than a converter.  The converters are generally used to convert a value from one Type to another Type, for instance, from a boolean to a value of System.Windows.Visibility or from a Color to a Brush.

To create a MarkupExtension class, you derive from the abstract MarkupExtension class.  There is one method to override, called ProvideValue.  A default constructor is required unless you intend to only support attribute usages of the class.  If you want to support arguments in the usage, might also need to define additional constructors to match the settable properties.

Attached is a sample WPF application with code that implements the MarkupExtension and IValueConverters I’ve mentioned in this short write-up.

Sample Code

Using Style Triggers to Give ToolTips to DataGrid Columns

Working with a DataGrid on a recent project, I had to get ToolTips on all of the DataGrid column headers.  At first go around, I had used the first thing that came to my head which was to insert a TextBlock into my Header and give it a ToolTip.

TextBlock in Header
  1. <DataGridTemplateColumn.Header>
  2.     <TextBlock Text="Column B" ToolTip="A short explanation of Column B"/>
  3. </DataGridTemplateColumn.Header>

 

This worked OK for a while, I got my expected result which was a ToolTip when a user moused over the header…although it wasn’t exactly what I wanted because they had to mouse over the text of my header, but I was OK with that.

Later on, another requirement came in, to allow the users to choose which columns were visible and which were hidden and that is when the flaw in my first approach was exposed (more on this later) and I found an easier way using a trigger to display tool tips and didn’t require creating a TextBlock in DataGridTemplateColumn.Header. 

ToolTip from Style Trigger
  1. <Style TargetType="{x:Type DataGridColumnHeader}">
  2.     <Style.Triggers>
  3.         <Trigger Property="IsMouseOver" Value="True">
  4.             <Setter Property="ToolTip" Value="{Binding Column.(ToolTipService.ToolTip), RelativeSource={RelativeSource Self}}"/>
  5.         </Trigger>
  6.     </Style.Triggers>
  7. </Style>
  8. <!– Use ToolTipService.ToolTip to assign the tooltip value –>
  9. <DataGridTemplateColumn x:Name="colA" Width="40*" IsReadOnly="True" Header="Column A" ToolTipService.ToolTip="Explanation of Column A">