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





5.00/5 (1投票)
用于 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
- 可以直接使用。
抽象
基类 +Int
和Double
数据类型的实现。- 如果希望绑定到数值“
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;
}
}
}
历史
第一个版本,可能会根据新的发现和需求进行更新。:)