I'm developing a custom text box for currency editing.
I've seen some ready to use ones, but they're complicated and/or not really usable, forcing you to bad practices (such as hard coding the name that's supposed to be used on the control).
So I've decided to do it myself, but I'm having trouble to work with the binding options, since the property assigned to the binding attribute must be a decimal, but the Text property of the TextBox control accepts strings.
The answer I thought was, maybe, override the access methods (getters and setters) to the Text property in the base class (TextBox), but it is not allowed.
My binding should be set to the value, that sets the text property of the TextBox formatting it as text (with the currency symbols and everything) on the go, but converting it back to a numeric datatype on the Get method.
This is what I've achieved so far:
public class CurrencyTextBox : TextBox
{
private bool IsValidKey(Key key)
{
int k = (int)key;
return ((k >= 34 && k <= 43) //digits 0 to 9
|| (k >= 74 && k <= 83) //numeric keypad 0 to 9
|| (k == 2) //back space
|| (k == 32) //delete
);
}
private void Format()
{
//formatting decimal to currency text here
//Done! no problems here
}
private void FormatBack()
{
//formatting currency text to decimal here
//Done! no problems here
}
private void ValueChanged(object sender, TextChangedEventArgs e)
{
this.Format();
}
private void MouseClicked(object sender, MouseButtonEventArgs e)
{
this.Format();
// Prevent changing the caret index
this.CaretIndex = this.Text.Length;
e.Handled = true;
}
private void MouseReleased(object sender, MouseButtonEventArgs e)
{
this.Format();
// Prevent changing the caret index
this.CaretIndex = this.Text.Length;
e.Handled = true;
}
private void KeyPressed(object sender, KeyEventArgs e)
{
if (IsValidKey(e.Key))
e.Handled = true;
if (Keyboard.Modifiers != ModifierKeys.None)
return;
this.Format();
}
private void PastingEventHandler(object sender, DataObjectEventArgs e)
{
// Prevent copy/paste
e.CancelCommand();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Disable copy/paste
DataObject.AddCopyingHandler(this, PastingEventHandler);
DataObject.AddPastingHandler(this, PastingEventHandler);
this.CaretIndex = this.Text.Length;
this.PreviewKeyUp += KeyPressed;
this.PreviewMouseDown += MouseClicked;
this.PreviewMouseUp += MouseReleased;
this.TextChanged += ValueChanged;
this.Format();
}
}
and this is the XAML:
<MyNamespace:CurrencyTextBox x:Name="TxbCurrency" Text="{Binding Path=DataContext.Element.Currency, ValidatesOnDataErrors=True}" />
So far so good! The binding from the decimal property to the TextBox text's is "right on". But how to get the decimal back from the text after it's editing is now the problem.
The binding from decimal to the .Text uses boxing to hide the ToString() method.
Question here:
How can I overload the Parse() method from decimal in this case to use my FormatBack() method to get the decimal from the TextBox's Text?
Well, for future purposes, if anybody is stuck with the same trouble, here's the complete code for the currency text box. Feel free to use it, modify it, sell it (don't think it's valuable, thou), or play with it as much as you want!
/*
* the necessary usings:
* using System.Globalization;
* using System.Windows;
* using System.Windows.Controls;
* using System.Windows.Input;
* using System.Threading;
* And don't forget to change the currency settings on the XAML
* or in the defaults (on the contructor)
* It's set by default to Brazilian Real (R$)
*/
public class CurrencyTextBox : TextBox
{
public CurrencyTextBox()
{
CurrencySymbol = "R$ ";
CurrencyDecimalPlaces = 2;
DecimalSeparator = ",";
ThousandSeparator = ".";
Culture = "pt-BR";
}
public string CurrencySymbol { get; set; }
private int CurrencyDecimalPlaces { get; set; }
public string DecimalSeparator { get; set; }
public string ThousandSeparator { get; set; }
public string Culture { get; set; }
private bool IsValidKey(int k)
{
return (k >= 34 && k <= 43) //digits 0 to 9
|| (k >= 74 && k <= 83) //numeric keypad 0 to 9
|| (k == 2) //back space
|| (k == 32) //delete
;
}
private string Format(string text)
{
string unformatedString = text == string.Empty ? "0,00" : text; //Initial state is always string.empty
unformatedString = unformatedString.Replace(CurrencySymbol, ""); //Remove currency symbol from text
unformatedString = unformatedString.Replace(DecimalSeparator, ""); //Remove separators (decimal)
unformatedString = unformatedString.Replace(ThousandSeparator, ""); //Remove separators (thousands)
decimal number = decimal.Parse(unformatedString) / (decimal)Math.Pow(10, CurrencyDecimalPlaces); //The value will have 'x' decimal places, so divide it by 10^x
unformatedString = number.ToString("C", CultureInfo.CreateSpecificCulture(Culture));
return unformatedString;
}
private decimal FormatBack(string text)
{
string unformatedString = text == string.Empty ? "0.00" : text;
unformatedString = unformatedString.Replace(CurrencySymbol, ""); //Remove currency symbol from text
unformatedString = unformatedString.Replace(ThousandSeparator, ""); //Remove separators (thousands);
CultureInfo current = Thread.CurrentThread.CurrentUICulture; //Let's change the culture to avoid "Input string was in an incorrect format"
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(Culture);
decimal returnValue = decimal.Parse(unformatedString);
Thread.CurrentThread.CurrentUICulture = current; //And now change it back, cuz we don't own the world, right?
return returnValue;
}
private void ValueChanged(object sender, TextChangedEventArgs e)
{
// Keep the caret at the end
this.CaretIndex = this.Text.Length;
}
private void MouseClicked(object sender, MouseButtonEventArgs e)
{
// Prevent changing the caret index
e.Handled = true;
this.Focus();
}
private void MouseReleased(object sender, MouseButtonEventArgs e)
{
// Prevent changing the caret index
e.Handled = true;
this.Focus();
}
private void KeyReleased(object sender, KeyEventArgs e)
{
this.Text = Format(this.Text);
this.Value = FormatBack(this.Text);
}
private void KeyPressed(object sender, KeyEventArgs e)
{
if (IsValidKey((int)e.Key))
return;
e.Handled = true;
this.CaretIndex = this.Text.Length;
}
private void PastingEventHandler(object sender, DataObjectEventArgs e)
{
// Prevent/disable paste
e.CancelCommand();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
DataObject.AddCopyingHandler(this, PastingEventHandler);
DataObject.AddPastingHandler(this, PastingEventHandler);
this.CaretIndex = this.Text.Length;
this.KeyDown += KeyPressed;
this.KeyUp += KeyReleased;
this.PreviewMouseDown += MouseClicked;
this.PreviewMouseUp += MouseReleased;
this.TextChanged += ValueChanged;
this.Text = Format(string.Empty);
}
public decimal? Value
{
get { return (decimal?)this.GetValue(ValueProperty); }
set { this.SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(decimal?),
typeof(CurrencyTextBox),
new FrameworkPropertyMetadata(new decimal?(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(ValuePropertyChanged)));
private static void ValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((CurrencyTextBox)d).Value = ((CurrencyTextBox)d).FormatBack(e.NewValue.ToString());
}
}
and the xaml:
<myNamespace:CurrencyTextBox
Value="{Binding Path=DataContext.MyDecimalProperty, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
CurrencySymbol="R$ "
Culture="pt-BR"
CurrencyDecimalPlaces="2"
DecimalSeparator=","
ThousandSeparator="." />
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With