因为需要在手机上配置IP,我需要一个界面输入IP地址,虽然直接使用TextBox,但是这样不太友好,我希望能够有和Windows网络设置上一样的IP输入框。所以决定写一个自定义控件。
设计控件外观
4个TextBox和3个显示“.”的TextBlock就可以了,结构很简单:
XAML代码如下:
<StackPanel Orientation="Horizontal" Height="72" >
<TextBox x:Name="TextOctet1"/>
<TextBlock Text="." />
<TextBox x:Name="TextOctet2"/>
<TextBlock Text="." />
<TextBox x:Name="TextOctet3"/>
<TextBlock Text="." />
<TextBox x:Name="TextOctet4"/>
</StackPanel>
因为IP地址里面只有数字和“.”,而且每位地址最长为3。为了限制输入将样式设置如下:
<UserControl.Resources>
<Style TargetType="TextBox">
<Setter Property="Width" Value="90"/>
<Setter Property="MaxLength" Value="3"/>
<Setter Property="TextAlignment" Value="Right"/>
<Setter Property="InputScope" Value="Number"/>
</Style>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="5,30,5,0"/>
</Style>
</UserControl.Resources>
InputScope设置为Number,限制输入数字和.。
InputScope
复习一下WP里面有一个叫做InputScope的依赖属性,这个是软输入面板设置 在WP里面非常有用的属性。支持的名称和功能如下:
AddressCity 城市地址的文本输入模式。
AddressCountryName 国家/地区的名称的文本输入模式。
AddressCountryShortName 国家/地区的缩写名称的文本输入模式。
AddressStateOrProvince 省/市/自治区的文本输入模式。
AddressStreet 街道地址的文本输入模式。
AlphanumericFullWidth 字母数字全角字符的文本输入模式。
AlphanumericHalfWidth 字母数字半角字符的文本输入模式。
ApplicationEnd 不支持。仅限在 用于 Windows Phone 的 Silverlight 中内部使用。
Bopomofo 汉语拼音语音转换系统的文本输入模式。
Chat 用于文本消息传递的 SIP 布局,可识别预定义的缩写。仅在 用于 Windows Phone 的 Silverlight 中受支持。
CurrencyAmount 货币数量的文本输入模式。
CurrencyAmountAndSymbol 货币数量和符号的文本输入模式。
CurrencyChinese 中国货币的文本输入模式。
Date 日历日期的文本输入模式。
DateDay 日历日期中数字日期的文本输入模式。
DateDayName 日历日期中日期名称的文本输入模式。
DateMonth 日历日期中数字月份的文本输入模式。
DateMonthName 日历日期中月份名称的文本输入模式。
DateYear 日历日期中年份的文本输入模式。
Default 输入命令的默认处理。
Digits 数字的文本输入模式。
EmailNameOrAddress 用于电子邮件名称或地址的 SIP 布局。仅在 用于 Windows Phone 的 Silverlight 中受支持。
EmailSmtpAddress 简单邮件传输协议 (SMTP) 电子邮件地址的文本输入模式。
EmailUserName 电子邮件用户名的文本输入模式。
EnumString 不支持。仅限在 用于 Windows Phone 的 Silverlight 中内部使用。
FileName 文件名的文本输入模式。
FullFilePath 文件完整路径的文本输入模式。
Hanja 朝鲜文汉字字符的文本输入模式。
Hiragana 平假名书写体系的文本输入模式。
KatakanaFullWidth 全角片假名字符的文本输入模式。
KatakanaHalfWidth 半角片假名字符的文本输入模式。
LogOnName 登录名的文本输入模式。
Maps 用于输入地图位置的 SIP 布局。仅在 用于 Windows Phone 的 Silverlight 中受支持。
NameOrPhoneNumber 用于 SMS“至”字段的 SIP 布局。仅在 用于 Windows Phone 的 Silverlight 中受支持。
Number 数字的文本输入模式。
NumberFullWidth 全角数字的文本输入模式。
OneChar 某个字符的文本输入模式。
Password 密码的文本输入模式。
PersonalFullName 个人的全名的文本输入模式。
PersonalGivenName 个人的名字的文本输入模式。
PersonalMiddleName 个人的中间名的文本输入模式。
PersonalNamePrefix 个人姓名前缀的文本输入模式。
PersonalNameSuffix 个人姓名后缀的文本输入模式。
PersonalSurname 个人的姓的文本输入模式。
PhraseList 词组列表的文本输入模式。
PostalAddress 邮寄地址的文本输入模式。
PostalCode 邮政编码的文本输入模式。
Private 不支持。仅限在 用于 Windows Phone 的 Silverlight 中的内部使用。
RegularExpression 正则表达式的文本输入模式。
Search 用于搜索查询的 SIP 布局。仅在 用于 Windows Phone 的 Silverlight 中受支持。
Srgs 语音识别语法规范 (SRGS) 的文本输入模式。
TelephoneAreaCode 区号的文本输入模式。
TelephoneCountryCode 电话的国家/地区代码的文本输入模式。
TelephoneLocalNumber 本地电话号码的文本输入模式。
TelephoneNumber 电话号码的文本输入模式。
Text 用于标准文本输入的软件输入面板 (SIP) 布局。仅在 用于 Windows Phone 的 Silverlight 中受支持。
Time 时间的文本输入模式。
TimeHour 小时的文本输入模式。
TimeMinorSec 时间的分钟或秒的文本输入模式。
Url 统一资源定位符 (URL) 的文本输入模式。
Xml XML 的文本输入模式。
Yomi 不支持。仅限在 用于 Windows Phone 的 Silverlight 中内部使用。
控件代码实现
先定义一下叫IPAddress的依赖属性,复习一下:
public static readonly DependencyProperty IPAddressProperty =DependencyProperty.Register("IPAddress", typeof(string), typeof(IPBox),new PropertyMetadata(String.Empty, IPPropertyChangedCallback));private static void IPPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs arg){var value = (string)arg.NewValue;if (string.IsNullOrWhiteSpace(value)) return;var ipBox = (IPBox)sender;ipBox.SetIPAddress(value);}[Description("获取或设置IP")][Category("Common Properties")]public string IPAddress{get { return (string)GetValue(IPAddressProperty); }set { SetValue(IPAddressProperty, value); }}
需要实现的功能有
- 限制输入范围为0~255
- 输入“.”自动将焦点跳到下一个TextBox
- 输入到3个数字的时候,自动将焦点跳到下一个TextBox
- 按Back键如果当前TextBox没有文本,需要自动将焦点跳到前一个TextBox
实现这些功能需要对每个TextBox监听TextChanged和KeyDown事件,并作相应处理。
完整代码如下:
public partial class IPBox
{
public static readonly DependencyProperty IPAddressProperty =
DependencyProperty.Register("IPAddress", typeof(string), typeof(IPBox),
new PropertyMetadata(String.Empty, IPPropertyChangedCallback));
private static void IPPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
{
var value = (string)arg.NewValue;
if (string.IsNullOrWhiteSpace(value)) return;
var ipBox = (IPBox)sender;
ipBox.SetIPAddress(value);
}
[Description("获取或设置IP")]
[Category("Common Properties")]
public string IPAddress
{
get { return (string)GetValue(IPAddressProperty); }
set { SetValue(IPAddressProperty, value); }
}
private readonly TextBox[] textBoxs;
private readonly string[] ipParts;
public IPBox()
{
InitializeComponent();
textBoxs = new[]
{
TextOctet1,
TextOctet2,
TextOctet3,
TextOctet4
};
ipParts = new string[4];
for (int i = 0; i < 4; i++)
{
var box = textBoxs[i];
box.Tag = i;
box.TextChanged += OnTextChanged;
box.KeyDown += OnKeyDown;
}
}
private void UpdateIPAddress()
{
for (int i = 0; i < 4; i++)
{
ipParts[i] = textBoxs[i].Text;
}
if (ipParts.Any(string.IsNullOrWhiteSpace)) return;
IPAddress = string.Format("{0}.{1}.{2}.{3}", TextOctet1.Text, TextOctet2.Text, TextOctet3.Text,
TextOctet4.Text);
}
private void SetIPAddress(string value)
{
IPAddress tmp;
if (!System.Net.IPAddress.TryParse(value, out tmp)) return;
string[] octets = value.Split('.');
for (int i = 0; i < 4; i++)
{
if (ipParts[i] != octets[i])
{
ipParts[i] = octets[i];
textBoxs[i].Text = octets[i];
}
}
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox)sender;
var index = (int)textBox.Tag;
if (string.IsNullOrWhiteSpace(textBox.Text)) return;
if (int.Parse(textBox.Text) > 255)
{
textBox.Text = "255";
textBox.Select(3, 0);
}
if (ipParts[index] == textBox.Text) return;
if (textBox.Text.Length == 3)
{
NextFocus(index);
}
UpdateIPAddress();
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
var textBox = (TextBox)sender;
if (e.PlatformKeyCode == 190) //.
{
e.Handled = true;
if (!string.IsNullOrEmpty(textBox.Text))
{
NextFocus((int)textBox.Tag);
}
return;
}
if (e.Key == Key.Back)
{
if (string.IsNullOrEmpty(textBox.Text))
{
e.Handled = true;
PrevFocus((int)textBox.Tag);
}
}
}
private void NextFocus(int index)
{
if (index >= 3) return;
var box = textBoxs[index + 1];
box.Focus();
box.Select(box.Text.Length, 0);
}
private void PrevFocus(int index)
{
if (index < 1) return;
var box = textBoxs[index - 1];
box.Focus();
box.Select(box.Text.Length, 0);
}
}