首页 文章

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

提问于
浏览
1

我正在开发一个用于货币编辑的自定义文本框 .
我很复杂和/或不能真正使用,迫使你做坏事(比如硬编码应该在控件上使用的名字) .
所以我've decided to do it myself, but I' m无法使用绑定选项,因为分配给绑定属性的属性必须是小数,但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 <= 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文本's is 1505998 . But how to get the decimal back from the text after it' s编辑现在是问题 .
从十进制到.Text的绑定使用装箱来隐藏ToString()方法 .
Question here: 如何在这种情况下从小数重载Parse()方法以使用我的FormatBack()方法从TextBox的Text中获取小数?

4 回答

  • 0

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

    然后绑定到这个新属性

  • 6

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

    /*
     * 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="." />
    
  • 2

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

  • 0

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

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

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

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

相关问题