I\'m trying to display a formatted decimal in a TextBox using data binding in WPF.
Goal 1: When setting a decimal property in code, display 2 decimal p
Try the WPF Extended Tookit Masked TextBox to implement an Input Mask: http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
Example:
<toolkit:MaskedTextBox Mask="(000) 000-0000" Value="(555) 123-4567"
IncludeLiterals="True" />
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:
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.
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:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace GUI.Helpers.Behaviors
{
public class DecimalPlaceHotkeyBehavior : Behavior<TextBox>
{
#region Methods
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewKeyDown -= AssociatedObject_PreviewKeyDown;
}
protected override Freezable CreateInstanceCore()
{
return new DecimalPlaceHotkeyBehavior();
}
#endregion
#region Event Methods
private void AssociatedObject_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.OemPeriod || e.Key == System.Windows.Input.Key.Decimal)
{
var periodIndex = AssociatedObject.Text.IndexOf('.');
if (periodIndex != -1)
{
AssociatedObject.CaretIndex = (periodIndex + 1);
e.Handled = true;
}
}
}
#endregion
#region Initialization
public DecimalPlaceHotkeyBehavior()
: base()
{
}
#endregion
}
}
I use it as follows:
<TextBox xmlns:Behaviors="clr-namespace:GUI.Helpers.Behaviors"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:0.00}}">
<i:Interaction.Behaviors>
<Behaviors:DecimalPlaceHotkeyBehavior></Behaviors:DecimalPlaceHotkeyBehavior>
</i:Interaction.Behaviors>
</TextBox>
Try to resolve that on ViewModel level. That it:
public class FormattedDecimalViewModel : INotifyPropertyChanged
{
private readonly string _format;
public FormattedDecimalViewModel()
: this("F2")
{
}
public FormattedDecimalViewModel(string format)
{
_format = format;
}
private string _someDecimalAsString;
// String value that will be displayed on the view.
// Bind this property to your control
public string SomeDecimalAsString
{
get
{
return _someDecimalAsString;
}
set
{
_someDecimalAsString = value;
RaisePropertyChanged("SomeDecimalAsString");
RaisePropertyChanged("SomeDecimal");
}
}
// Converts user input to decimal or initializes view model
public decimal SomeDecimal
{
get
{
return decimal.Parse(_someDecimalAsString);
}
set
{
SomeDecimalAsString = value.ToString(_format);
}
}
// Applies format forcibly
public void ApplyFormat()
{
SomeDecimalAsString = SomeDecimal.ToString(_format);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
SAMPLE
Xaml:
<TextBox x:Name="tb" Text="{Binding Path=SomeDecimalAsString, UpdateSourceTrigger=PropertyChanged}" />
Code behind:
public MainWindow()
{
InitializeComponent();
FormattedDecimalViewModel formattedDecimalViewModel = new FormattedDecimalViewModel { SomeDecimal = (decimal)2.50 };
tb.LostFocus += (s, e) => formattedDecimalViewModel.ApplyFormat(); // when user finishes to type, will apply formatting
DataContext = formattedDecimalViewModel;
}