工作中时长会遇到金额的输入框,网上有很多方案,其中很多采用自定义控件继承TextBox类,扩展校验。此处给出通过附加属性控制TextBox输入格式为金额的solution。
简单说下原理:
1.通过附加属性类自定义IsDecimal附加属性设置为True时回调函数。
2.在回调函数中获取该TextBox实例,并在TextChanged事件增加对该Text校验(校验方式很多,我采用的正则式匹配校验),若符合,通过。否则,将值设为最后一次符合校验的值。
这里有两个细节:
①关于存储上次的符合校验的值,我使用了一个Dictionary<TextBox,String>来存储。在上面的原理“1”中将实例加入到Dictionary中,并默认初始为String.Empty,若字典中已存在该实例则跳过。
②在原理“2”中,还需要对光标位置记录,若通过校验,需要更新Dictionary中该TextBox的值。若不通过,除了改为Dictionary中存储的上次值外,还需要将光标位置还原。【改善用户体验】
下面上代码:
/// <summary>
/// ID:Decimal附加属性,针对TextBox
/// Describe:附加属性控制TextBox仅输入金额,不可为负。小数点前最多14位,后最多两位
/// 注意:当使用Binding时,若StringFormat和UpdateSourceTrigger=PropertyChanged同时存在,则会出现系统导致的bug,如9.2111111...
/// Author:ybx
/// Date:2016-11-2 11:00:15
/// </summary>
class DecimalHelper
{
private static Dictionary<TextBox, string> _lastValueDictionary = new Dictionary<TextBox, string>();
private static string reg = @"^[1-9][0-9]{0,13}\.?[0-9]{0,2}$|^[0-9]?\.[0-9]{0,2}$|^$|^[0-9]$";
public static bool GetIsDecimal(DependencyObject obj)
{
return (bool)obj.GetValue(IsDecimalProperty);
}
public static void SetIsDecimal(DependencyObject obj, bool value)
{
obj.SetValue(IsDecimalProperty, value);
}
// Using a DependencyProperty as the backing store for IsDecimal. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsDecimalProperty =
DependencyProperty.RegisterAttached("IsDecimal", typeof(bool), typeof(TextBoxHelper), new PropertyMetadata(false, new PropertyChangedCallback(IsDecimalChanged)));
private static void IsDecimalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tb = d as TextBox;
if (!_lastValueDictionary.Keys.Contains(tb))
{
_lastValueDictionary.Add(tb, string.Empty);
}
tb.TextChanged -= DecimalCheck;
if ((bool)e.NewValue)
{
tb.TextChanged += DecimalCheck;
}
}
private static void DecimalCheck(object sender, TextChangedEventArgs e)
{
var txt = (sender as TextBox);
int index = txt.SelectionStart;
if (!new Regex(reg).IsMatch(txt.Text))
{
txt.Text = _lastValueDictionary[txt];
if (index - 1 >= 0)
txt.SelectionStart = index - 1;
}
else
{
_lastValueDictionary[txt] = txt.Text;
}
}
}
注意:
当使用Binding时,若StringFormat和UpdateSourceTrigger=PropertyChanged同时存在,则会出现系统导致的bug,如9.2111111...
例如
<Window x:Class="DecimalTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:txt="clr-namespace:DecimalTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox txt:DecimalHelper.IsDecimal="true" Text="{Binding Num,StringFormat={}{0:f},UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
此时由于系统机制,输入9.222222时,获取的TextBox的Text会被StringFormat格式为9.22,导致无法正确校验格式。暂时没有更好的方案。