在最近的Silverlight开发中,发现一个需求,需要一个只能够输入数字,并且能够对小数点后的位数进行控制并填充的控件,原有的TextBox并不能实现这一个功能,于是就决定自己实现一个,要包含的具体的功能如下:
l 只能输入0-9的数字和至多一个小数点;
l 能够屏蔽通过非正常途径的不正确输入(输入法,粘贴等);
l 能够控制小数点后的最大位数,超出位数则无法继续输入;
l 能够选择当小数点数位数不足时是否补0;
l 去除开头部分多余的0(为方便处理,当在开头部分输入0时,自动在其后添加一个小数点);
l 由于只能输入一个小数点,当在已有的小数点前再次按下小数点,能够跳过小数点;
在实现前参考了许多网上的相关资料,虽然没有找到具体的实现代码,但是还是得到了很多有用的思路,最终决定将上述功能分成两个部分来实现:其中控制只能输入数字的部分作为一个通用的TextBoxFilterBehavior进行设计;而格式化的部分则在NumericBox内部实现,下面首先介绍TextBoxFilterBehavior的实现。
在实现之前,首先查看了TextBox的相关事件,发现Silverlight中的事件比WPF来说有了很大的减少,没得选择,可用的事件只有KeyDown跟TextChanged。这里使用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控件的KeyDown和TextChanged事件,在里面对输入的文本进行了验证,如果没有通过验证,则将文本充值回上次正确的文本。由于在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类的实现思路及相关代码我将在下篇文章中给出,敬请关注。
(未完待续)