65.9K
CodeProject 正在变化。 阅读更多。
Home

"智能" WPF 文本框,带输入限制和验证

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2016 年 1 月 23 日

CPOL

1分钟阅读

viewsIcon

18129

用于 UI 输入验证的自定义 TextBox 控件

引言

编辑
-发现了一些小错误。(完成检查后会更新代码)
-实现了“Value”的依赖属性。(完成检查后会更新代码)
-已知问题:“Value” setter 在从 UI 更新文本时会被触发多次。
-已知问题:数字字符串格式不符合区域设置。
--------------------------------------------------------------------------------------------

我的 WPF 开发是从继承一个单人应用程序开始的,该应用程序使用 WPF,采用 WinForms 事件驱动方法,并且没有使用 WPF 提供的任何技术。 甚至都不应该提及架构,它与 MVVM 的差距,就像我们离殖民火星一样遥远。

我遇到的最令人恼火的问题之一是数值用户输入的验证、限制和格式化。 鉴于应用程序的性质,有些字段只能为正数,有些字段只能为整数,等等。

我需要一个解决方案,既能处理数据绑定,又能直接使用 UI 元素。 此外,我希望字符串值和数值都能随时可用。 最后,我需要通过按键/鼠标滚轮 + 不同的文本前景色来“微调”这些值。

以下是我的解决方案,可以根据具体需求轻松扩展和修改。
它可以做什么

  • 限制输入以匹配 Int/Double 正确的格式,禁用复制粘贴
  • IsPositiveOnly 设置器(“0”被认为是“正数”)
  • Normal/Modified/Error 文本前景色设置器
  • 使用“千位分隔符”显示数字(我处理的是大数字,所以这真的很有帮助)
  • 小数位数设置器(double)
  • 按键 Up/Down/P-Up/P-Down/鼠标滚轮 - 按预定义的百分比增加/减少值

Using the Code

  • 可以直接使用。
  • 抽象基类 + IntDouble 数据类型的实现。
  • 如果希望绑定到数值“Value”,则应实现依赖属性。

基类

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace CustomControls
{
    public abstract class SmartTextBox : TextBox
    {
        public string ParamName { get; set; }
        public bool IsValid { get; protected set; }
        public bool IsPosOnly { get; set; }
        public bool IsSetModifiedFG { get; set; }

        public int FinePercent { get; set; }
        public int CoarsePercent { get; set; }
        public int WheelPercent { get; set; }

        private static readonly BrushConverter _BrushConverter = new BrushConverter();
        private const string _DefaultBgColor = "#3CFFFFFF";
        protected SmartTextBox()
        {
            IsPosOnly = true;
            IsSetModifiedFG = true;
            FinePercent = 1;
            CoarsePercent = 10;
            WheelPercent = 3;

            DataObject.AddCopyingHandler(this, 
            (sender, args) => args.CancelCommand()); //disable copy-paste
            DataObject.AddPastingHandler(this, 
            (sender, args) => args.CancelCommand()); //disable copy-paste
            this.PreviewMouseRightButtonUp += 
            (sender, args) => args.Handled = true;    //disable right-click context menu
            this.PreviewKeyDown += OnPreviewKeyDown;
            this.TextChanged += OnTextChanged;
            this.MouseWheel += OnMouseWheel;

            this.Background = (Brush)_BrushConverter.ConvertFromString(_DefaultBgColor);
        }

        protected abstract void ApplyField();
        protected abstract void CancelField();

        public void SetNormalForeground()
        {
            this.Foreground = Brushes.Black;
        }

        public void SetModifiedForeground()
        {
            this.Foreground = Brushes.RoyalBlue;
        }

        public void SetErrorForeground()
        {
            this.Foreground = Brushes.Red;
        }

        private void OnMouseWheel(object sender, MouseWheelEventArgs mouseWheelEventArgs)
        {
            if (mouseWheelEventArgs.Delta > 0)
                IncrementByPercent(WheelPercent);
            else
                DecrementByPercent(WheelPercent);
        }

        protected virtual void OnTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
        {
            IsValid = !(this.Text == "" || 
            this.Text == "-" || this.Text == "Not supported");
        }

        protected virtual void OnPreviewKeyDown(object sender, KeyEventArgs k)
        {
            if (k.Key == Key.Enter)
            {
                ApplyField();
            }
            else if (k.Key == Key.Escape)
            {
                CancelField();
            }
            else if (k.Key == Key.Up)
            {
                IncrementByPercent(FinePercent);
            }
            else if (k.Key == Key.Down)
            {
                DecrementByPercent(FinePercent);
            }
            else if (k.Key == Key.PageUp)
            {
                k.Handled = true;
                IncrementByPercent(CoarsePercent);
            }
            else if (k.Key == Key.PageDown)
            {
                k.Handled = true;
                DecrementByPercent(CoarsePercent);
            }
            else if (!IsPosOnly && (k.Key == Key.Subtract || k.Key == Key.OemMinus))
            {
                if (this.Text.StartsWith("-") || this.CaretIndex != 0)
                    k.Handled = true;
            }
            else
            {
                int keyInt = (int)k.Key;
                if ((keyInt >= 34 && keyInt <= 43) || 
                (keyInt >= 74 && keyInt <= 83)) //numerics
                {
                    if (this.CaretIndex == 0 && 
                    (this.Text.StartsWith("-") || this.Text.StartsWith(".")))
                        k.Handled = true;
                }
                else if (!(k.Key == Key.Back || k.Key == Key.Delete || k.Key == Key.Left || 
                k.Key == Key.Right || k.Key == Key.Tab || k.Key == Key.Home || k.Key == Key.End))
                {
                    k.Handled = true;
                }
            }
        }

        protected abstract void IncrementByPercent(int percent);

        protected abstract void DecrementByPercent(int percent);
    }
}

INT 处理实现

using System;
using System.Windows.Controls;

namespace CustomControls
{
    public class SmartIntTextBox : SmartTextBox
    {
        public Action<smartinttextbox> ApplyFieldEvent;
        public Action<smartinttextbox> CancelFieldEvent;
        public Action<smartinttextbox> DataChangedEvent;

        private int _Value;
        public int Value
        {
            get { return _Value; }
            protected set
            {
                _Value = value;

                int ci = this.CaretIndex;
                int len = this.Text.Length;
                this.Text = _Value.ToString("N0");
                this.CaretIndex = ci + (this.Text.Length - len);    //account for added commas

                if (DataChangedEvent != null)
                    DataChangedEvent(this);
            }
        }

        protected override void ApplyField()
        {
            if (ApplyFieldEvent != null)
                ApplyFieldEvent(this);
        }

        protected override void CancelField()
        {
            if (CancelFieldEvent != null)
                CancelFieldEvent(this);
        }

        protected override void OnTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
        {
            base.OnTextChanged(sender, textChangedEventArgs);
            if(!IsValid)
                return;

            if (!int.TryParse(this.Text.Replace(",", ""), out _Value))
            {
                _Value = 0;
                IsValid = false;
                SetErrorForeground();
                return;
            }

            Value = _Value;

            IsValid = true;
            if (IsSetModifiedFG)
                SetModifiedForeground();
        }
        protected override void IncrementByPercent(int percent)
        {
            if (_Value == 0)
            {
                ++Value;
                return;
            }

            double ratio = ((double)percent + 100) / 100;
            Value = _Value > 0 ? (int)Math.Ceiling(_Value * ratio) : (int)Math.Ceiling(_Value / ratio);
        }

        protected override void DecrementByPercent(int percent)
        {
            if (!IsPosOnly && _Value == 0)
            {
                --Value;
                return;
            }

            double ratio = ((double)percent + 100) / 100;
            Value = _Value > 0 ? (int)Math.Floor(_Value / ratio) : (int)Math.Floor(_Value * ratio);
        }
    }
}

Double 处理实现

using System;
using System.Windows.Controls;
using System.Windows.Input;

namespace CustomControls
{
    public class SmartDoubleTextBox : SmartTextBox
    {
        public Action<smartdoubletextbox> ApplyFieldEvent;
        public Action<smartdoubletextbox> CancelFieldEvent;
        public Action<smartdoubletextbox> DataChangedEvent;

        private string _Format = "{0:#,##0.##}";
        private int _DecPlaces = 2;
        public int DecPlaces
        {
            get { return _DecPlaces; }
            set
            {
                _DecPlaces = value;
                _Format = "{0:#,##0.";
                for (int ii = 0; ii < _DecPlaces; ++ii)
                    _Format += "#";
                _Format += "}";

                this.Text = string.Format(_Format, _Value);
            }
        }
        
        private double _Value;
        public double Value
        {
            get { return _Value; }
            protected set
            {
                _Value = value;

                int ci = this.CaretIndex;
                int len = this.Text.Length;
                this.Text = string.Format(_Format, _Value);
                this.CaretIndex = ci + (this.Text.Length - len);    //account for added commas

                if (DataChangedEvent != null)
                    DataChangedEvent(this);
            }
        }

        protected override void ApplyField()
        {
            if (ApplyFieldEvent != null)
                ApplyFieldEvent(this);
        }

        protected override void CancelField()
        {
            if (CancelFieldEvent != null)
                CancelFieldEvent(this);
        }

        protected override void OnTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
        {
            base.OnTextChanged(sender, textChangedEventArgs);
            if (!IsValid)
                return;

            if (!double.TryParse(this.Text.Replace(",", ""), out _Value))
            {
                _Value = 0;
                IsValid = false;
                SetErrorForeground();
                return;
            }

            if (!(this.Text.EndsWith(".") || 
            (this.Text.Contains(".") && this.Text.EndsWith("0"))))
                Value = _Value;

            IsValid = true;
            if (IsSetModifiedFG)
                SetModifiedForeground();
        }

        protected override void OnPreviewKeyDown(object sender, KeyEventArgs k)
        {
            int decIdx = this.Text.IndexOf(".");
            if (k.Key == Key.Decimal || k.Key == Key.OemPeriod)
            {
                if (decIdx != -1)
                    k.Handled = true;
                return;
            }
            if (decIdx >= 0)
            {
                int keyInt = (int)k.Key;
                if (this.CaretIndex > decIdx &&
                    this.Text.Length - decIdx - 1 >= _DecPlaces &&
                    ((keyInt >= 34 && keyInt <= 43) || 
                    (keyInt >= 74 && keyInt <= 83)))
                {
                    k.Handled = true;
                    return;
                }
            }

            base.OnPreviewKeyDown(sender, k);
        }

        protected override void IncrementByPercent(int percent)
        {
            if (Math.Abs(_Value) < 0.00001)
            {
                ++Value;
                return;
            }

            double ratio = ((double)percent + 100) / 100;
            Value = _Value > 0 ? _Value * ratio : _Value / ratio;
        }

        protected override void DecrementByPercent(int percent)
        {
            if (!IsPosOnly && Math.Abs(_Value) < 0.00001)
            {
                --Value;
                return;
            }

            double ratio = ((double)percent + 100) / 100;
            Value = _Value > 0 ? _Value / ratio : _Value * ratio;
        }
    }
}

历史

第一个版本,可能会根据新的发现和需求进行更新。:)

© . All rights reserved.