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

Posted in C#, WPF | Tagged , , | Leave a comment

Filtering Items in a WPF ComboBox

I was building a WPF application for a client which had a few ComboBoxes with large amounts of items as options in them.  The client asked for the users to have the ability to filter the items in the drop down, but still force them to choose an item in the list.  A search of the internet yielded some solutions, but each solution required me to set the ComboBox.IsEditable property to “True”.  In each solution I found and tried doing this always made the user experience a little weird, allowing them to type in whatever they wanted, but still only allowing them to choose an item within the list.

UnFiltered-FilteringComboBox

FilteringComboBox

What I did to finally solve the problem was actually pretty simple and I was surprised none of the solutions I came across didn’t go this route.  I created a new UserControl to handle this implementation so it would be easier to reuse throughout the application and others in the future. 

The first part of it was creating a new Style for the ComboBox, so that inside the Popup (PART_Popup) portion of the ControlTemplate can include my filtering TextBox.

<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
    <Microsoft_Windows_Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=MainGrid}">
        <Border x:Name="DropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
            <Grid RenderOptions.ClearTypeHint="Enabled">
                <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                    <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=DropDownBorder}" Height="{Binding ActualHeight, ElementName=DropDownBorder}" Width="{Binding ActualWidth, ElementName=DropDownBorder}"/>
                </Canvas>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <DockPanel LastChildFill="True" Margin="0,5,10,5" Grid.Row="0">
                        <TextBlock DockPanel.Dock="Left" Text="Filter:" Margin="0,0,5,0" />
                        <TextBox x:Name="DropDownFilterTextBox" />
                    </DockPanel>
                    <ScrollViewer x:Name="DropDownScrollViewer" Grid.Row="1">
                        <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </ScrollViewer>
                </Grid>
            </Grid>

        </Border>
    </Microsoft_Windows_Themes:SystemDropShadowChrome>
</Popup>

The other piece of this is filtering the items in the within the ComboBox as they type in the filter TextBox.  To do this, I attach to the TextChanged event on it.

protected void DropDownFilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    TextBox textBox = ((TextBox)sender);

    ComboBox cbo = ((TextBox)sender).TemplatedParent as ComboBox;
    cbo.Items.Filter += a =>
    {
        if (a.ToString().ToLower().StartsWith(textBox.Text.ToLower()))
        {
            return true;
        }
        return false;
    };
}

Get the VS 2010 Samples Solution for this blog which includes the WPF ComboBox with an items Filter

Posted in C#, WPF, XAML | 6 Comments

WPF DataGrid, Read-Only Row

A common requirement for a DataGrid is to have entire rows that are read-only.  You may have a list of records in your DataGrid and some need to be locked for various reasons.  Unfortunately, in the XAML definition of a DataGrid, there isn’t a way to mark a row as being Read-Only.  To accomplish this without tying my view to my model or rewriting the DataGrid, I’m utilizing the BeginningEdit event, the Tag property of the DataGridRow, and created a style for the DataGridRow with a DataTrigger to put everything together.

First thing I did was to make sure my Model had an IsReadOnly property to latch onto, in the attached sample code, I did this by adding the property to one of the Customer in the Northwind database . 

CustomerReadOnlyProperty

Next, using a Trigger in my DataGridRow style, if the data item’s IsReadOnly property evaluates to “True” then I set the DataGridRow ‘s Tag to a string I can look for in my BeginningEdit event handler.  In the sample project attached and in the code snippets below, I used the string “ReadOnly”.  For a visual cue, I also set the HeaderTemplate to show an image to indicate that the row is locked. 

XAML Styles
  1. <!– Row Header Template to show a Locked icon.–>
  2. <DataTemplate x:Key="rowHeaderTemplate">
  3.     <StackPanel Orientation="Horizontal">
  4.         <Image x:Name="editImage" Source="Images/lock.gif" Width="16" Margin="1,0" />
  5.     </StackPanel>
  6. </DataTemplate>
  7. <!– DataGridRow Style –>
  8. <Style x:Key="ReadOnlyCheck" TargetType="{x:Type DataGridRow}">
  9.     <Style.Triggers>
  10.         <DataTrigger Binding="{Binding IsReadOnly}" Value="True">
  11.             <Setter Property="Tag" Value="ReadOnly" />
  12.             <Setter Property="HeaderTemplate" Value="{StaticResource rowHeaderTemplate}">
  13.             </Setter>
  14.         </DataTrigger>
  15.     </Style.Triggers>
  16. </Style>

 

CustomerReadOnlyVisual

Finally, in my BeginningEdit event handler for my DataGrid, I look at the Row going into Edit Mode, if its Tag property has been set to “ReadOnly” I cancel the event so it never makes it into edit mode.  This enforces the IsReadOnly property from my mode.

BeginningEdit Event Handler
  1. /// <summary>
  2. /// Event Handler for BeginningEdit Event of DataGrid.  If the Row entering into Edit Mode has a Tag set to "ReadOnly",
  3. /// cancel the edit.
  4. /// </summary>
  5. /// <param name="sender">Reference to the DataGrid</param>
  6. /// <param name="e">Instance of DataGridBeginningEditEventArgs</param>
  7. private void dataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
  8. {
  9.     if (((DataGridRow)e.Row).Tag != null && ((DataGridRow)e.Row).Tag.ToString() == "ReadOnly")
  10.     {
  11.         e.Cancel = true;
  12.     }
  13. }

 

Sample Code Download

Posted in WPF, XAML | Tagged | 3 Comments

WPF Textbox Select All on Focus

A while back I was creating an application that needed to allow fast and accurate input.  In WPF, the default behavior of the TextBox on focus is to put the cursor where it was the last time the TextBox had lost focus or if it hasn’t had focus yet, at the beginning.  The users of the application wanted this to be changed so that when the TextBox got focus, all current text was selected.  I found various ways to do this, but am putting this blog post together to get everything I ended up doing all in one place for easy access.

Basic Focus – GotFocus Event vs. GotKeyboardFocus Event

There is a decision to be made here.  In the application that had me working on this stuff for the first time, I chose the GotKeyboardFocus Event.  The difference is really that GotFocus will fire when your TextBox gets focus from within the application (or Window), but not when your Window/Application regains focus.  GotKeyboardFocus will fire anytime your TextBox gains or regains keyboard focus.   So if you are working with something like AvalonDock and have various sections (i.e. DockableContent) that accept input and want your TextBoxes to select all when each section regains focus use the GotKeyboardFocus event.  If your application is one where users will be multi-tasking a lot causing your window to constantly lose and regain focus and you don’t want all of the text to get selected each time they come back to your application, you’d use the GotFocus event.

Sample Handler:

GotFocus or GotKeyboardFocus
  1. private void TextBox_SelectAllText(object sender, RoutedEventArgs e)
  2. {
  3.     ((TextBox)sender).SelectAll();
  4. }

 

Mouse Focus:  PreviewMouseDown Event

Just using GotFocus or GotKeyboardFocus doesn’t give us what we want when someone clicks into the TextBox.  You’ll see it quickly select all, then deselect and put the cursor where you clicked.  Using the PreviewMouseDown event, you can check if the TextBox already has Keyboard focus and if it doesn’t, select everything.  The example below also checks for a triple click…and selects all if it is a triple click.

PreviewMouseDown Event Handler
  1. /// <summary>
  2. /// Handles PreviewMouseDown Event.  Selects all on Triple click.  
  3. /// If SelectAllOnEnter is true, when the textbox is clicked and doesn't already have keyboard focus, selects all
  4. /// </summary>
  5. /// <param name="sender"></param>
  6. /// <param name="e"></param>
  7. private void TextBox_SelectivelyIgnoreMouseButton(object sender, MouseButtonEventArgs e)
  8. {
  9.     // If its a triple click, select all text for the user.
  10.     if (e.ClickCount == 3)
  11.     {
  12.         TextBox_SelectAllText(sender, new RoutedEventArgs());
  13.         return;
  14.     }
  15.  
  16.     // Find the TextBox
  17.     DependencyObject parent = e.OriginalSource as UIElement;
  18.     while (parent != null && !(parent is TextBox))
  19.     {
  20.         parent = System.Windows.Media.VisualTreeHelper.GetParent(parent);
  21.     }
  22.  
  23.     if (parent != null)
  24.     {
  25.         if (parent is TextBox)
  26.         {
  27.             var textBox = (TextBox)parent;
  28.             if (!textBox.IsKeyboardFocusWithin)
  29.             {
  30.                 // If the text box is not yet focussed, give it the focus and
  31.                 // stop further processing of this click event.
  32.                 textBox.Focus();
  33.                 e.Handled = true;
  34.             }
  35.         }
  36.     }
  37. }

 

Application-Wide Event Handlers

Now of course, in every Window, User Control, View, etc., you don’t want to have to add these event handlers to each and every TextBox.  To easily and quickly attach these handlers to every TextBox within your application you can use the EventManager class to register these event handlers to the TextBox class application-wide.  I put these in the event handler for the Application.Startup event.

Application.Startup Handler
  1. private void Application_Startup(object sender, StartupEventArgs e)
  2. {
  3.     EventManager.RegisterClassHandler(typeof(TextBox), TextBox.GotFocusEvent, new RoutedEventHandler(TextBox_SelectAllText));
  4.     EventManager.RegisterClassHandler(typeof(TextBox), TextBox.PreviewMouseDownEvent, new MouseButtonEventHandler(TextBox_SelectivelyIgnoreMouseButton));
  5. }

Posted in C#, WPF | Tagged | 8 Comments

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

Posted in C#, WPF, XAML | Tagged | Leave a comment

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">

Posted in WPF, XAML | Tagged , , | 2 Comments

Choosing foreground using Luminosity Contrast Ratio

Sample Source Code

When a background color is chosen by the user, you can use a converter that calculates luminosity contrast ratio to choose the best foreground color for the background given.  In the code sample attached, I use a converter that does this getting the luminosity contrast for the background with a white foreground and a black foreground.  Whichever has the better luminosity contrast ratio, is the foreground color I use.

I got formula for the luminosity contrast ratio from the W3C Recommendation for Web Content Accessiblity Guidelines (WCAG) 2.0.  Outside of code, the formula looks like:

(L1 + 0.05) / (L2 + 0.05), where

  • L1 is the relative luminance of the lighter color
  • L2 is the relative luminance of the darker color

Next question I had was “how do I calculate relative luminance”?  The formula for this is also within the guidelines.  First of all, a definition:  relative luminance is “the relative brightness of any point in a colorspace, normalized to 0 for the darkest black and 1 for the lightest white.”

It can be calculated using the formula below:

Relative Luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B, where:

R:  If RsRGB <= 0.03928 then R = RsRGB  / 12.92 else R = ((RsRGB  + 0.055)/1.055)2.4
G:  If GsRGB <= 0.03928 then G = GsRGB  / 12.92 else G = ((GsRGB  + 0.055)/1.055)2.4
B:  If BsRGB <= 0.03928 then B = BsRGB  / 12.92 else B = ((BsRGB  + 0.055)/1.055)2.4

Ok, but another variable is introduced here, how are RsRGB, GsRGB, BsRGB each calculated?  This is a bit easier and is defined in the same area as the relative luminance formula.

  • RsRGB = R8bit/255
  • GsRGB = G8bit/255
  • BsRGB = B8bit/255

Finally, now how does all of this look in C#?

Calculate Luminosity Contrast
  1. private double LuminosityContrast(Color foreground, Color background)
  2. {
  3.     var foregroundLuminosity = RelativeLuminance(foreground.R, foreground.G, foreground.B);
  4.     var backgroundLuminosity = RelativeLuminance(background.R, background.G, background.B);
  5.  
  6.     if (foregroundLuminosity > backgroundLuminosity)
  7.     {
  8.         return (foregroundLuminosity + 0.05) / (backgroundLuminosity + 0.05);
  9.     }
  10.     else
  11.     {
  12.         return (backgroundLuminosity + 0.05) / (foregroundLuminosity + 0.05);
  13.     }
  14. }
  15.  
  16. private double RelativeLuminance(byte r, byte g, byte b)
  17. {
  18.     double rs = ((double)r / 255);
  19.     double gs = ((double)g / 255);
  20.     double bs = ((double)b / 255);
  21.     double R = 0;
  22.     double G = 0;
  23.     double B = 0;
  24.  
  25.     R = (rs <= 0.03928) ? (double)(rs / 12.92) : Math.Pow(((rs + 0.055) / 1.055), 2.4);
  26.     G = (gs <= 0.03928) ? (double)(gs / 12.92) : Math.Pow(((gs + 0.055) / 1.055), 2.4);
  27.     B = (bs <= 0.03928) ? (double)(bs / 12.92) : Math.Pow(((bs + 0.055) / 1.055), 2.4);
  28.  
  29.     return ((double)(0.2126 * R)) + ((double)(0.7152 * G)) + ((double)(0.0722 * B));
  30. }

 

And now, I can use this in my Convert method of my IValueConverter:

IValueConverter – Convert
  1. public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  2. {
  3.     if (value != null && value is Color)
  4.     {
  5.         Color backgroundColor = (Color)value;
  6.  
  7.         // Calculate the luminosity constrast ratio for the given color against black and white.
  8.         var blackContrast = LuminosityContrast(Colors.Black, backgroundColor);
  9.         var whiteContrast = LuminosityContrast(Colors.White, backgroundColor);
  10.  
  11.         // return a solid color brush using either black or white, depending on which had the higher luminosity constrast ratio.
  12.         return blackContrast >= whiteContrast ? new SolidColorBrush(Colors.Black) : new SolidColorBrush(Colors.White);
  13.     }
  14.     else
  15.     {
  16.         return new SolidColorBrush(Colors.Black);
  17.     }
  18. }

 

Finally, I can (declare and) use my converter in XAML to pick the better color for use against the background the user of my application has chosen.

Using the converter
  1. <Converters:ForegroundColorFromBackgroundColorConverter x:Key="foregroundDetector" />
  2. <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
  3.            Foreground="{Binding SelectedBrush.Color, Converter={StaticResource ResourceKey=foregroundDetector}, Mode=OneWay}">
  4.     <Run FontWeight="Bold">Selected Color</Run>
  5. </TextBlock>

Posted in C#, WPF | Tagged , | 5 Comments