I'm trying to display a formatted decimal in a TextBox using data binding in WPF.
Goals
Goal 1: When setting a decimal property in code, display 2 decimal places in the TextBox.
Goal 2: When a user interacts with (types in) the TextBox, don't piss him/her off.
Goal 3: Bindings must update source on PropertyChanged.
Attempts
Attempt 1: No formatting.
Here we're starting nearly from scratch.
<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />
Violates Goal 1. SomeDecimal = 4.5
will show "4.50000" in the TextBox.
Attempt 2: Use StringFormat in the Binding.
<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}" />
Violates Goal 2. Say SomeDecimal is 2.5, and the TextBox is displaying "2.50". If we select all and type "13.5" we end up with "13.5.00" in the TextBox because the formatter "helpfully" inserts decimals and zeros.
Attempt 3: use Extended WPF Toolkit's MaskedTextBox.
http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
Assuming I'm reading the documentation correctly, the mask ##0.00 means "two optional digits, followed by a required digit, a decimal point, and two more required digits. This forces me to say "the largest possible number that can go into this TextBox is 999.99" but let's say I'm ok with that.
<xctk:MaskedTextBox Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" Mask="##0.00" />
Violates Goal 2. The TextBox starts with ___.__
and selecting it and typing 5.75 yields 575.__
. The only way to get 5.75 is to select the TextBox and type <space><space>5.75
.
Attempt 4: use Extended WPF Toolkit's DecimalUpDown spinner.
http://wpftoolkit.codeplex.com/wikipage?title=DecimalUpDown
<xctk:DecimalUpDown Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" FormatString="F2" />
Violates Goal 3. DecimalUpDown happily ignores UpdateSourceTrigger=PropertyChanged. One of the Coordinators on the Extended WPF Toolkit Codeplex page suggests a modified ControlTemplate at http://wpftoolkit.codeplex.com/discussions/352551/. This satisfies Goal 3, but violates Goal 2, exhibiting the same behavior as in Attempt 2.
Attempt 5: Using style datatriggers, only use formatting if user is not editing.
Binding to a double with StringFormat on a TextBox
Even if this one satisfied all three goals, I wouldn't want to use it. (A) Textboxes become 12 lines each instead of 1, and my application contains many, many textboxes. (B) All my textboxes already have a Style attribute which points to a global StaticResource which sets Margin, Height, and other things. (C) You may have noticed the code below sets the binding Path twice, which violates the DRY principal.
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Path=SomeDecimal, StringFormat=F2}" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Text" Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
All these uncomfortable things aside...
Violates Goal 2. First, clicking on a TextBox which displays "13.50" suddenly makes it display "13.5000", which is unexpected. Second, if starting with a blank TextBox, and I type "13.50", the TextBox will contain "1350". I can't explain why, but pressing the period key doesn't insert decimals if the cursor is at the right end of the string in the TextBox. If the TextBox contains a string with length > 0, and I reposition the cursor to anywhere except the right end of the string, I can then insert decimal points.
Attempt 6: Do it myself.
I'm about to embark on a jouney of pain and suffering, by either subclassing TextBox, or creating an attached property, and writing the formatting code myself. It will be full of string manipulation, and cause substantial hairloss.
Does anyone have any suggestions for formatting decimals bound to textboxes that satisfies all the above goals?
Try to resolve that on ViewModel level. That it:
SAMPLE
Xaml:
Code behind:
I created the following custom behavior to move the users cursor to after the decimal point when using
StringFormat={}{0:0.00}
, which forces a decimal place to be present, however this can cause the following issue:I have hacked around this using a custom behavior that will move the users cursor to after the decimal place when they press the . key:
I use it as follows:
Try the WPF Extended Tookit Masked TextBox to implement an Input Mask: http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
Example: