Silverlight开发中的疑难杂症-控件设计篇-如何实现一个NumericBox(上)

在最近的Silverlight开发中,发现一个需求,需要一个只能够输入数字,并且能够对小数点后的位数进行控制并填充的控件,原有的TextBox并不能实现这一个功能,于是就决定自己实现一个,要包含的具体的功能如下:

只能输入0-9的数字和至多一个小数点;

能够屏蔽通过非正常途径的不正确输入(输入法,粘贴等);

能够控制小数点后的最大位数,超出位数则无法继续输入;

能够选择当小数点数位数不足时是否补0

去除开头部分多余的0(为方便处理,当在开头部分输入0时,自动在其后添加一个小数点);

由于只能输入一个小数点,当在已有的小数点前再次按下小数点,能够跳过小数点;

在实现前参考了许多网上的相关资料,虽然没有找到具体的实现代码,但是还是得到了很多有用的思路,最终决定将上述功能分成两个部分来实现:其中控制只能输入数字的部分作为一个通用的TextBoxFilterBehavior进行设计;而格式化的部分则在NumericBox内部实现,下面首先介绍TextBoxFilterBehavior的实现。

在实现之前,首先查看了TextBox的相关事件,发现Silverlight中的事件比WPF来说有了很大的减少,没得选择,可用的事件只有KeyDownTextChanged。这里使用KeyDown事件来处理按键产生的输入,TextChanged事件来处理通过其它方式进行的输入(其实完全可以都在TextChanged事件中进行处理,我也想不通为什么自己选择分开进行~)。

由于要做成一个通用的TextBox筛选行为,所以这里添加了一个筛选类型的枚举选项TextBoxFilterOptions,里面暂时只有数字、小数点跟字母,留待以后扩充,实现代码如下:

TextBoxFilterOptions
 1 /// <summary>
 2     /// TextBox筛选选项
 3     /// </summary>
 4     [Flags]
 5     public enum TextBoxFilterOptions
 6     {
 7         /// <summary>
 8         /// 不采用任何筛选
 9         /// </summary>
10         None = 0,
11         /// <summary>
12         /// 数字类型不参与筛选
13         /// </summary>
14         Numeric = 1,
15         /// <summary>
16         /// 字母类型不参与筛选
17         /// </summary>
18         Character = 2,
19         /// <summary>
20         /// 小数点不参与筛选
21         /// </summary>
22         Dot = 4,
23         /// <summary>
24         /// 其它类型不参与筛选
25         /// </summary>
26         Other = 8
27     }
28 
29     /// <summary>
30     /// TextBox筛选选项枚举扩展方法
31     /// </summary>
32     public static class TextBoxFilterOptionsExtension
33     {
34         /// <summary>
35         /// 在全部的选项中是否包含指定的选项
36         /// </summary>
37         /// <param name="allOptions">所有的选项</param>
38         /// <param name="option">指定的选项</param>
39         /// <returns></returns>
40         public static bool ContainsOption(this TextBoxFilterOptions allOptions, TextBoxFilterOptions option)
41         {
42             return (allOptions & option) == option;
43         }
44     }
45 

其中ContainsOption方法是一个提供了方便的枚举运算的扩展方法。

       另外还增加了一个键盘操作的帮助类KeyboardHelper,在实现时发现Silverlight中的按键比WPF中少了很多,甚至连主键盘上的小数点键(句号)都无法识别,这里使用了一个平台相关的keycode进行判别,如果有更好的方法请给我留言。代码如下:

KeyboardHelper
  1 /// <summary>
  2     /// 键盘操作帮助类
  3     /// </summary>
  4     public class KeyboardHelper
  5     {
  6         /// <summary>
  7         /// 键盘上的句号键
  8         /// </summary>
  9         public const int OemPeriod = 190;
 10 
 11         #region Fileds
 12 
 13         /// <summary>
 14         /// 控制键
 15         /// </summary>
 16         private static readonly List<Key> _controlKeys = new List<Key>
 17                                                             {
 18                                                                 Key.Back,
 19                                                                 Key.CapsLock,
 20                                                                 Key.Ctrl,
 21                                                                 Key.Down,
 22                                                                 Key.End,
 23                                                                 Key.Enter,
 24                                                                 Key.Escape,
 25                                                                 Key.Home,
 26                                                                 Key.Insert,
 27                                                                 Key.Left,
 28                                                                 Key.PageDown,
 29                                                                 Key.PageUp,
 30                                                                 Key.Right,
 31                                                                 Key.Shift,
 32                                                                 Key.Tab,
 33                                                                 Key.Up
 34                                                             };
 35 
 36         #endregion
 37 
 38         /// <summary>
 39         /// 是否是数字键
 40         /// </summary>
 41         /// <param name="key">按键</param>
 42         /// <returns></returns>
 43         public static bool IsDigit(Key key)
 44         {
 45             bool shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
 46             bool retVal;
 47             //按住shift键后,数字键并不是数字键
 48             if (key >= Key.D0 && key <= Key.D9 && !shiftKey)
 49             {
 50                 retVal = true;
 51             }
 52             else
 53             {
 54                 retVal = key >= Key.NumPad0 && key <= Key.NumPad9;
 55             }
 56             return retVal;
 57         }
 58 
 59         /// <summary>
 60         /// 是否是控制键
 61         /// </summary>
 62         /// <param name="key">按键</param>
 63         /// <returns></returns>
 64         public static bool IsControlKeys(Key key)
 65         {
 66             return _controlKeys.Contains(key);
 67         }
 68 
 69         /// <summary>
 70         /// 是否是小数点
 71         /// Silverlight中无法识别问号左边的那个小数点键
 72         /// 只能识别小键盘中的小数点
 73         /// </summary>
 74         /// <param name="key">按键</param>
 75         /// <returns></returns>
 76         public static bool IsDot(Key key)
 77         {
 78             return key == Key.Decimal;
 79         }
 80 
 81         /// <summary>
 82         /// 是否是小数点
 83         /// </summary>
 84         /// <param name="key">按键</param>
 85         /// <param name="keyCode">平台相关的按键代码</param>
 86         /// <returns></returns>
 87         public static bool IsDot(Key key, int keyCode)
 88         {
 89             return IsDot(key) || (key == Key.Unknown && keyCode == OemPeriod); 
 90         }
 91 
 92         /// <summary>
 93         /// 是否是字母键
 94         /// </summary>
 95         /// <param name="key">按键</param>
 96         /// <returns></returns>
 97         public static bool IsCharacter(Key key)
 98         {
 99             return key >= Key.A && key <= Key.Z;
100         }
101     }
102 

最后是TextBoxFilterBehavior的实现,关于Behavior的相关概念,大家可以参见Blend的帮助文档以及MSDN的相关文章,园子里也有很多大牛对它进行了详细的阐述,我就不深入讲解了。在这里,我们首先添加了一个TextBoxFilterOptions类型的依赖属性,用来设置过滤的选项,如下:

Dependency Properties
 1 #region Dependency Properties
 2 
 3         /// <summary>
 4         /// TextBox筛选选项,这里选择的为过滤后剩下的按键
 5         /// 控制键不参与筛选,可以多选组合
 6         /// </summary>
 7         public TextBoxFilterOptions TextBoxFilterOptions
 8         {
 9             get { return (TextBoxFilterOptions)GetValue(TextBoxFilterOptionsProperty); }
10             set { SetValue(TextBoxFilterOptionsProperty, value); }
11         }
12 
13         // Using a DependencyProperty as the backing store for TextBoxFilterOptions.  This enables animation, styling, binding, etc...
14         public static readonly DependencyProperty TextBoxFilterOptionsProperty =
15             DependencyProperty.Register("TextBoxFilterOptions"typeof(TextBoxFilterOptions), typeof(TextBoxFilterBehavior), new PropertyMetadata(TextBoxFilterOptions.None));
16 
17         #endregion
18 

然后注册了关联的TextBox控件的KeyDownTextChanged事件,在里面对输入的文本进行了验证,如果没有通过验证,则将文本充值回上次正确的文本。由于在TextChanged事件中并没有WPF里面的TextChange数组,所以无法拿到本次变更的文本,只能选择处理完整的Text完整的代码如下:

TextBoxFilterBehavior
  1 /// <summary>
  2     /// TextBox筛选行为,过滤不需要的按键
  3     /// </summary>
  4     public class TextBoxFilterBehavior : Behavior<TextBox>
  5     {
  6         private string _prevText = string.Empty;
  7 
  8         public TextBoxFilterBehavior()
  9         {
 10         }
 11 
 12         #region Dependency Properties
 13 
 14         /// <summary>
 15         /// TextBox筛选选项,这里选择的为过滤后剩下的按键
 16         /// 控制键不参与筛选,可以多选组合
 17         /// </summary>
 18         public TextBoxFilterOptions TextBoxFilterOptions
 19         {
 20             get { return (TextBoxFilterOptions)GetValue(TextBoxFilterOptionsProperty); }
 21             set { SetValue(TextBoxFilterOptionsProperty, value); }
 22         }
 23 
 24         // Using a DependencyProperty as the backing store for TextBoxFilterOptions.  This enables animation, styling, binding, etc...
 25         public static readonly DependencyProperty TextBoxFilterOptionsProperty =
 26             DependencyProperty.Register("TextBoxFilterOptions"typeof(TextBoxFilterOptions), typeof(TextBoxFilterBehavior), new PropertyMetadata(TextBoxFilterOptions.None));
 27 
 28         #endregion
 29 
 30         protected override void OnAttached()
 31         {
 32             base.OnAttached();
 33 
 34             this.AssociatedObject.KeyDown += new KeyEventHandler(AssociatedObject_KeyDown);
 35             this.AssociatedObject.TextChanged += new TextChangedEventHandler(AssociatedObject_TextChanged);
 36         }
 37 
 38         protected override void OnDetaching()
 39         {
 40             base.OnDetaching();
 41 
 42             this.AssociatedObject.KeyDown -= new KeyEventHandler(AssociatedObject_KeyDown);
 43             this.AssociatedObject.TextChanged -= new TextChangedEventHandler(AssociatedObject_TextChanged);
 44         }
 45 
 46         #region Events
 47 
 48         /// <summary>
 49         /// 处理通过其它手段进行的输入
 50         /// </summary>
 51         /// <param name="sender"></param>
 52         /// <param name="e"></param>
 53         void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
 54         {
 55             //如果符合规则,就保存下来
 56             if (IsValidText(this.AssociatedObject.Text))
 57             {
 58                 _prevText = this.AssociatedObject.Text;
 59             }
 60             //如果不符合规则,就恢复为之前保存的值
 61             else 
 62             {
 63                 int selectIndex = this.AssociatedObject.SelectionStart - (this.AssociatedObject.Text.Length - _prevText.Length);                
 64                 this.AssociatedObject.Text = _prevText;
 65                 this.AssociatedObject.SelectionStart = selectIndex;
 66             }
 67 
 68         }
 69 
 70         /// <summary>
 71         /// 处理按键产生的输入
 72         /// </summary>
 73         /// <param name="sender"></param>
 74         /// <param name="e"></param>
 75         void AssociatedObject_KeyDown(object sender, KeyEventArgs e)
 76         {
 77             bool handled = true;
 78             //不进行过滤
 79             if (TextBoxFilterOptions == TextBoxFilterOptions.None ||
 80                 KeyboardHelper.IsControlKeys(e.Key))
 81             {
 82                 handled = false;
 83             }
 84             //数字键
 85             if (handled && TextBoxFilterOptions.ContainsOption(TextBoxFilterOptions.Numeric))
 86             {
 87                 handled = !KeyboardHelper.IsDigit(e.Key);
 88             }
 89             //小数点
 90             if (handled && TextBoxFilterOptions.ContainsOption(TextBoxFilterOptions.Dot))
 91             {
 92                 handled = !(KeyboardHelper.IsDot(e.Key, e.PlatformKeyCode) && !_prevText.Contains("."));
 93                 if (KeyboardHelper.IsDot(e.Key, e.PlatformKeyCode) && _prevText.Contains("."))
 94                 {
 95                     //如果输入位置的下一个就是小数点,则将光标跳到小数点后面
 96                     if (this.AssociatedObject.SelectionStart< this.AssociatedObject.Text.Length && _prevText[this.AssociatedObject.SelectionStart] == '.')
 97                     {
 98                         this.AssociatedObject.SelectionStart++;
 99                     }                    
100                 }
101             }
102             //字母
103             if (handled && TextBoxFilterOptions.ContainsOption(TextBoxFilterOptions.Character))
104             {
105                 handled = !KeyboardHelper.IsDot(e.Key);
106             }
107             e.Handled = handled;
108         }
109 
110         #endregion
111 
112         #region Private Methods
113 
114         /// <summary>
115         /// 判断是否符合规则
116         /// </summary>
117         /// <param name="c"></param>
118         /// <returns></returns>
119         private bool IsValidChar(char c)
120         {
121             if (TextBoxFilterOptions == TextBoxFilterOptions.None)
122             {
123                 return true;
124             }
125             else if (TextBoxFilterOptions.ContainsOption(TextBoxFilterOptions.Numeric) &&
126                 '0' <= c && c <= '9')
127             {
128                 return true;
129             }
130             else if (TextBoxFilterOptions.ContainsOption(TextBoxFilterOptions.Dot) &&
131                 c == '.')
132             {
133                 return true;
134             }
135             else if (TextBoxFilterOptions.ContainsOption(TextBoxFilterOptions.Character)) 
136             {
137                 if (('A' <= c && c <= 'Z'|| ('a' <= c && c <= 'z'))
138                 {
139                     return true;
140                 }
141             }
142             return false;
143         }
144 
145         /// <summary>
146         /// 判断文本是否符合规则
147         /// </summary>
148         /// <param name="text"></param>
149         /// <returns></returns>
150         private bool IsValidText(string text)
151         {
152             //只能有一个小数点
153             if (text.IndexOf('.'!= text.LastIndexOf('.'))
154             {
155                 return false;
156             }
157 
158             foreach (char c in text)
159             {
160                 if (!IsValidChar(c))
161                 {
162                     return false;
163                 }
164             }
165             return true;
166         }
167 
168         #endregion
169     }
170 

TextBoxFilterBehavior类主要的实现思路和代码就是这样,希望能给需要的朋友一定的帮助,NumericBox类的实现思路及相关代码我将在下篇文章中给出,敬请关注。

(未完待续)

posted on 2010-03-09 00:05  yingql  阅读(1914)  评论(5编辑  收藏  举报

导航