0

In our WPF application, there are several textboxes where users ought to input only currency. It's easy to force the textbox to format the input as it goes in:

<TextBox Text="{Binding CostOut, StringFormat='{}{0:C}' />

However, this formats so quickly that it causes unexpected effects to the user as the text is type, because the numbers before and after the decimal seem to be treated almost as separate fields.

To counter this, we added a delay:

<TextBox Text="{Binding CostOut, StringFormat='{}{0:C}', Delay=1000  />

Which worked better as most people had finished typing before their numbers got formatted. However, the application is complex and deals with important financial data, and some users think carefully about the numbers as they're typed. For these slow typers, this delay still caused their input to get reformatted mid-type.

I'm unwilling to go further down the "delay" route as we'll eventually get to a point where it doesn't get formatted before someone saves. I found and tried a WPF CurrencyTextBox which was rejected as a solution because the "cash register" style typing was too unfamiliar.

Currently the proposed solution is to remove all formatting from the application and format only on save. This feels drastic to me, and I can't help wondering if there's a better solution?

Bob Tway
  • 9,301
  • 17
  • 80
  • 162
  • What .net framework version are you using? i think there were some problems in 4.0 – Pikoh Jan 16 '17 at 10:19
  • @Pikoh it's 4.5 – Bob Tway Jan 16 '17 at 10:30
  • I guess behavior would do better stuff neither StringFormat. Try to use [Attached Behavior](http://stackoverflow.com/questions/1268552/how-do-i-get-a-textbox-to-only-accept-numeric-input-in-wpf) from @WillP answer – Shakra Jan 16 '17 at 10:48

3 Answers3

2

You could try setting the desired format when the textBox loses it's focus on the LostFocus event of your textBox. It'll allow the user to type as long as he/she has to, and won't be as drastic as to format it on the save button.

  • 3
    And if you want to know how to get that event without breaking the MVVM-Pattern just let me know. Then I write a small attached property for you. – Tomtom Jan 16 '17 at 10:33
  • @Tomtom that would be helpful, thanks - was the reason I didn't go down this route myself originally – Bob Tway Jan 16 '17 at 10:40
2

As mentioned in a comment I've created a small example on how to bind the LostFocus-Event to a ICommand-property in your ViewModel.

The attached-property looks like:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TextBoxLostFocusBehaviorExample
{
    internal static class TextBoxExtensions
    {
        public static readonly DependencyProperty LostFocusCommandProperty = DependencyProperty.RegisterAttached(
            "LostFocusCommand",
            typeof(ICommand),
            typeof(TextBoxExtensions),
            new PropertyMetadata(default(ICommand), OnLostFocusCommandChanged));

        private static void OnLostFocusCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox textBox = d as TextBox;
            if (textBox == null)
            {
                return;
            }

            if (e.NewValue != null)
            {
                textBox.LostFocus += TextBoxOnLostFocus;
            }
        }

        private static void TextBoxOnLostFocus(object sender, RoutedEventArgs e)
        {
            TextBox textBox = sender as TextBox;
            if (textBox == null)
            {
                return;
            }

            ICommand command = GetLostFocusCommand(textBox);
            command.Execute(null);
        }

        public static void SetLostFocusCommand(DependencyObject element, ICommand value)
        {
            element.SetValue(LostFocusCommandProperty, value);
        }

        public static ICommand GetLostFocusCommand(DependencyObject element)
        {
            return (ICommand)element.GetValue(LostFocusCommandProperty);
        }
    }
}

In your ViewModel you then have a property of type ICommand which can look like:

private ICommand lostFocusCommand;
public ICommand LostFocusCommand
{
    get { return lostFocusCommand ?? (lostFocusCommand = new RelayCommand(p => CostOutLostFocus())); }
}

And the CostOutLostFocus-Method will be called when the LostFocus-Event is triggered.

The usage of the attached property in the view looks like:

<TextBox Text="{Binding CostOut, Mode=TwoWay}" TextBoxLostFocusBehaviorExample:TextBoxExtensions.LostFocusCommand="{Binding LostFocusCommand}"/>

TextBoxLostFocusBehaviorExample is the namespace where the class of the attached property is defined.

Tomtom
  • 9,087
  • 7
  • 52
  • 95
0

Currently the proposed solution is to remove all formatting from the application and format only on save. This feels drastic to me, and I can't help wondering if there's a better solution?

You could update the source property and apply the formatting when the user steps out of the TextBox by simply setting the UpdateSourceTrigger property of the binding to its default value of LostFocus in the XAML markup:

<TextBox Text="{Binding CostOut, StringFormat={}{0:C}, UpdateSourceTrigger=LostFocus}" />

This should at least a bit better than applying the formatting when saving.

Another option would be to use some kind of masked TextBox. There is no one built-in but you could try the one that is available in the open-sourced Extended WPF Toolkit: https://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox&referringTitle=Home

There are commercial options available as well: http://docs.telerik.com/devtools/wpf/controls/radmaskedinput/features/maskedinput-controls/currency

mm8
  • 163,881
  • 10
  • 57
  • 88