WPF如何使用validation和绑定创建自定义文本框

我正在开发一个用于货币编辑的自定义文本框。
我已经看到一些准备使用它们,但它们很复杂和/或实际上不可用,迫使你做坏事(比如硬编码应该在控件上使用的名称)。
所以我决定自己做,但是我无法使用绑定选项,因为分配给绑定属性的属性必须是小数,但TextBox控件的Text属性接受字符串。
我想的答案可能是覆盖基类(TextBox)中Text属性的访问方法(getter和setter),但是不允许这样做。
我的绑定应该设置为值,它设置TextBox的text属性,将其格式化为文本(带有货币符号和所有内容),但将其转换回Get方法上的数值数据类型。
这是我到目前为止所取得的成就:

public class CurrencyTextBox : TextBox { private bool IsValidKey(Key key) { int k = (int)key; return ((k >= 34 && 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(); } } 

这是XAML:

  

到现在为止还挺好! 从decimal属性到TextBox文本的绑定是“正确的”。 但是如何在编辑后从文本中取回小数现在是问题所在。
从十进制到.Text的绑定使用装箱来隐藏ToString()方法。
问题:在这种情况下如何从小数重载Parse()方法以使用我的FormatBack()方法从TextBox的Text中获取小数?

像这样创建新的Dependency Property

 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 x = (CurrencyTextBox)d; x.Value = (decimal?)e.NewValue; } 

然后绑定到这个新属性

好吧,为了将来的目的,如果有人遇到同样的问题,这里是货币文本框的完整代码。 随意使用它,修改它,卖它(不要认为它有价值,你),或者随意玩它!

 /* * 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()); } } 

和xaml:

  

看看这篇文章,我认为它会对你有所帮助。 http://www.codeproject.com/Articles/15239/Validation-in-Windows-Presentation-Foundation

或者你可以把这个

 private static bool IsTextAllowed(string text) { Regex regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text return !regex.IsMatch(text); } 

并在PreviewTextInput事件中放这个

e.Handled =!IsTextAllowed(e.Text);

我不认为这实际上是可能的,除了一个只允许数字的盒子的简单情况。 理想情况下,您想要一个只能包含有效条目的框,但小数包含一些本身无效的字符(如“ – ”和“。”)。 用户无法在不将框置于无效状态的情况下键入“ – ”开始。

同样,他们可以输入“1”,然后删除1并使盒子处于不确定状态。 当然,它会导致validation错误和红色边框,但您的视图模型仍然认为值为1,并且不知道问题。

对于正整数,您只能允许数字并在空白时自动插入零(尽管这有点不友好)

对于小数和负整数,我认为你能做的最好的事情是约束用户可以键入的键,但你仍然需要将你的数字属性包装在一个字符串中并validation它 – 无论是按下OK按钮,还是理想的实现INotifyDataError显示错误并禁用“确定”按钮。