Binding a Floating Point Property to a TextBox With a StringFormat in WPF

1 Comment

By Ken Walker Senior Software Engineer

Microsoft’s WPF bindings are a fine piece of technology. They are flexible and powerful, allowing changes to your objects’ properties to ripple through the rest of your application with a minimal amount of coding effort.

There is one small but very common situation however, where bindings behave poorly: binding a TextBox control to a floating point property (either Single or Double). When you bind these without specifying a StringFormat parameter, you cannot enter a decimal point, so you cannot actually enter a floating point number. When you do specify a StringFormat, you cannot enter numbers naturally; you have to edit the string to look like the number you want.

Some examples of the unnatural editing style when using a StringFormat parameter (here, F2) are:

  • Replacing the number does not work. If you highlight the number that is there and type “1.25”, you end up with “1.25.00”.
  • Deleting the decimal point does not remove it, but moves it. If you start at the end of the number “12.88” and backspace three times, you get the number “1200.00”.

When we ran across this problem on a recent project, I did what any programmer does these days for non-domain-related problems—I fired up Google to see who had already fixed this problem for me. Darn! Although a lot of people had complained about the problem, no one had a solution.

The only potential solution offered was to set the KeepTextBoxDisplaySynchronizedWithTextProperty property on the FrameworkCompatibilityPreferences class to false. Doing so allows you to enter a decimal point when you do not specify a StringFormat parameter on your floating point bindings. But it does not allow you to enforce a format, and therefore a precision, on your numbers. This may work for your project, but it was not sufficient for ours.

A quick survey of third-party libraries showed no help from them, either. So if this was going to be fixed, we would have to fix it ourselves, which is shared at my Github site here.   The code is illustrated below.

When you analyze the specifics of the undesirable behaviors, the problem boils down to the decimal point not being respected as a special character on input. If the cursor is at the decimal point, and you type a period, you probably don’t want a second decimal point in your number—you are acknowledging the one that is already there. If you are deleting or backspacing and come to the decimal point, perhaps you really do want to delete it; but if one is going to get added back at some other location, why not leave this one where it is and just move on?

This is the approach our FloatingPointTextBox control takes. It overrides System.Windows.Controls.TextBox and replaces certain keystrokes with movement of the caret. The benefit of our FloatingPointTextBox solution is that it won’t allow the user to add any extra decimal points, and won’t let them delete the last one. Problem solved.

// If the user pressed Delete and we're to the left of the only
// decimal point, skip over the decimal point
if (CaretIndex < Text.Length && Text[CaretIndex] == '.' 
    && Text.HasOnlyOne('.'))
{
    CaretIndex++;
    e.Handled = true;
}

Well, almost solved. Testing showed an additional irritating case: using a custom format string (comprised of 0 and # characters), highlighting the entire number and typing in a negative number. After you enter the negative sign and the first digit, the TextBox moves the cursor to the end of the string. So if your format string is 00.00 and you highlight whatever number is in the text box and type “-12.34” you end up with “-1.00.34”.

The fix is to watch specifically for this condition and move the cursor back to the left of the decimal point in time to catch the third character being typed. This is also included in our code—see the uses of the NegativeIntegerPending and NegativeFractionPending properties.

protected override void OnTextChanged(TextChangedEventArgs e)
{
    base.OnTextChanged(e);

    // If we've determined this negative number requires
    // caret adjustment, adjust it
    int decimalIndex = Text.IndexOf('.');
    if (NegativeIntegerPending && decimalIndex > -1)
    {
        CaretIndex = decimalIndex;
    }
    else if (NegativeFractionPending && decimalIndex > -1)
    {
        CaretIndex = decimalIndex + 2;
    }
}

This control has reduced the frustration our users were feeling when entering floating point numbers into our UI. Hopefully, it can give you and your users the same benefits.

Author: bridge360blog

Software Changes Everything.... Bridge360 improves and develops custom application software. We specialize in solving complex problems at every phase of the software development lifecycle, removing roadblocks to help our clients’ software and applications reach their full potential in any market. The Bridge360 customer base includes software companies and world technology leaders, leading system integrators, federal and state government agencies, and small to enterprise businesses across the globe. Clients spanning industries from legal to healthcare, automotive to energy, and high tech to high fashion count on us to clear a path for success. Bridge360 was founded in 2001 (as Austin Test) and is headquartered in Austin, Texas with offices in Beijing, China.

One thought on “Binding a Floating Point Property to a TextBox With a StringFormat in WPF

  1. We encountered this issue at my previous job. My solution was to only allow numeric entry and let the format handle everything else. For instance, if the format is ‘$#,##0.00’, typing numbers would start filling in from the right to left. I had to add some special logic around replacing characters for selections and when the caret wasn’t at the end, but it worked well.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s