【问题标题】:WPF How to create a Custom Textbox with validation and bindingWPF 如何使用验证和绑定创建自定义文本框
【发布时间】:2015-01-15 21:22:53
【问题描述】:

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

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

这是 XAML:

<MyNamespace:CurrencyTextBox x:Name="TxbCurrency" Text="{Binding Path=DataContext.Element.Currency, ValidatesOnDataErrors=True}" />

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

【问题讨论】:

  • 我想让用户输入任何文本,即使是字符串,如果他没有添加十进制值,当失去焦点时会调用
  • 我猜这个问题不清楚。问题在于绑定,而不是格式化逻辑。格式化文本已经可以了...
  • 如果是这种情况,创建新的Dependency Property 调用它的值并将你的货币绑定到它

标签: c# wpf xaml binding custom-controls


【解决方案1】:

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

/*
 * 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:

<myNamespace:CurrencyTextBox
    Value="{Binding Path=DataContext.MyDecimalProperty, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
    CurrencySymbol="R$ "
    Culture="pt-BR"
    CurrencyDecimalPlaces="2"
    DecimalSeparator=","
    ThousandSeparator="." />

【讨论】:

  • 哇,这真的很有帮助!我在这里仍然遇到另一个问题:当我将此控件放在我的 WPF UI 上时,我绑定的初始值不会显示,直到我明确告诉控件更新。有没有办法在你的代码中实现它?编辑:我在值的设置器中添加了以下内容:if(!this.IsFocused) this.Text = String.Format("{0:0.00}", value)
  • 为什么要添加事件处理程序而不是仅仅覆盖基本处理程序?而不是“private void KeyReleased(object sender, KeyEventArgs e)”,为什么不使用“protected override void OnKeyUp(KeyEventArgs e)”?
【解决方案2】:

像这样创建新的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;
}

然后绑定到这个新属性

【讨论】:

  • 这解决了问题。在 ValuePropertyChanged() 中,我使用 FormatBack 设置了 Value 属性,瞧!竖起大拇指!你不能投票,因为我的“新人”名声!
【解决方案3】:

看看这篇文章,我认为它会对你有所帮助。 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);

【讨论】:

    【解决方案4】:

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

    类似地,他们可以输入“1.”,然后删除 1 并使框处于不确定状态。当然,它会导致验证错误和红色边框,但您的视图模型仍然认为该值为 1,并且没有意识到问题。

    对于正整数,只能允许数字,空白时自动插入零(虽然这有点不友好)

    对于小数和负整数,我认为你能做的最好的事情是限制用户可以键入的键,但你仍然需要将你的 number 属性包装在一个字符串中并验证它——无论是当按下 OK 按钮时,或者理想地实现 INotifyDataError 来显示错误并禁用 OK 按钮。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-02
      • 2013-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-18
      相关资源
      最近更新 更多