WPF IP地址输入控件的实现
一、前言
WPF没有内置IP地址输入控件,因此我们需要通过自己定义实现。
我们先看一下IP地址输入控件有什么特性:

- 输满三个数字焦点会往右移
- 键盘←→可以空光标移动
- 任意位置可复制整段IP地址,且支持x.x.x.x格式的粘贴赋值
- 删除字符会自动向左移动焦点
知道以上特性,我们就可以开始动手了。
二、构成
Grid+TextBox*4+TextBlock*3
通过这几个控件的组合,我们完成IP地址输入控件的功能。
界面代码如下:
<UserControl
x:Class="IpAddressControl.IpAddressControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:IpAddressControl"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Margin="10,0"
d:DesignHeight="50"
d:DesignWidth="800"
mc:Ignorable="d" Background="White">
<UserControl.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock
Margin="1,2"
DockPanel.Dock="Right"
FontSize="{DynamicResource ResourceKey=Heading4}"
FontWeight="Bold"
Foreground="Red"
Text="" />
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
<Style x:Key="CustomTextBoxTextStyle" TargetType="TextBox">
<Setter Property="MaxLength" Value="3" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Trigger.Setters>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Background" Value="Red" />
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="30" />
<ColumnDefinition Width="10" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition Width="10" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition Width="10" />
<ColumnDefinition MinWidth="30" />
</Grid.ColumnDefinitions> <!-- Part 1 -->
<TextBox
Grid.Column="0"
BorderThickness="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
x:Name="part1"
PreviewKeyDown="Part1_PreviewKeyDown"
local:FocusChangeExtension.IsFocused="{Binding IsPart1Focused, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}"
Style="{StaticResource CustomTextBoxTextStyle}"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<TextBox.Text>
<Binding Path="Part1" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IPRangeValidationRule Max="255" Min="0" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
FontSize="15"
Text="."
VerticalAlignment="Center"
/> <!-- Part 2 -->
<TextBox
Grid.Column="2"
x:Name="part2"
BorderThickness="0"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
PreviewKeyDown="Part2_KeyDown"
local:FocusChangeExtension.IsFocused="{Binding IsPart2Focused}"
Style="{StaticResource CustomTextBoxTextStyle}"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<TextBox.Text>
<Binding Path="Part2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IPRangeValidationRule Max="255" Min="0" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock
Grid.Column="3"
HorizontalAlignment="Center"
FontSize="15"
Text="."
VerticalAlignment="Center"/> <!-- Part 3 -->
<TextBox
Grid.Column="4"
x:Name="part3"
BorderThickness="0"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
PreviewKeyDown="Part3_KeyDown"
local:FocusChangeExtension.IsFocused="{Binding IsPart3Focused}"
Style="{StaticResource CustomTextBoxTextStyle}"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<TextBox.Text>
<Binding Path="Part3" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IPRangeValidationRule Max="255" Min="0" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock
Grid.Column="5"
HorizontalAlignment="Center"
FontSize="15"
Text="."
VerticalAlignment="Center"/> <!-- Part 4 -->
<TextBox
Grid.Column="6"
x:Name="part4"
BorderThickness="0"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
PreviewKeyDown="Part4_KeyDown"
local:FocusChangeExtension.IsFocused="{Binding IsPart4Focused}"
Style="{StaticResource CustomTextBoxTextStyle}"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<TextBox.Text>
<Binding Path="Part4" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IPRangeValidationRule Max="255" Min="0" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</UserControl>
查看代码
三、验证输入格式
界面中为TextBox添加了CustomTextBoxTextStyle及validationTemplate样式,当输入格式不正确时,控件就会应用该样式。
通过自定义规则IPRangeValidationRule来验证输入的内容格式是否要求。
自定义规则代码如下:
public class IPRangeValidationRule : ValidationRule
{
private int _min;
private int _max; public int Min
{
get { return _min; }
set { _min = value; }
} public int Max
{
get { return _max; }
set { _max = value; }
} public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int val = ;
var strVal = (string)value;
try
{
if (strVal.Length > )
{
if (strVal.EndsWith("."))
{
return CheckRanges(strVal.Replace(".", ""));
} // Allow dot character to move to next box
return CheckRanges(strVal);
}
}
catch (Exception e)
{
return new ValidationResult(false, "Illegal characters or " + e.Message);
} if ((val < Min) || (val > Max))
{
return new ValidationResult(false,
"Please enter the value in the range: " + Min + " - " + Max + ".");
}
else
{
return ValidationResult.ValidResult;
}
} private ValidationResult CheckRanges(string strVal)
{
if (int.TryParse(strVal, out var res))
{
if ((res < Min) || (res > Max))
{
return new ValidationResult(false,
"Please enter the value in the range: " + Min + " - " + Max + ".");
}
else
{
return ValidationResult.ValidResult;
}
}
else
{
return new ValidationResult(false, "Illegal characters entered");
}
}
}
查看代码
四、控制焦点变化
在界面代码中我通过local:FocusChangeExtension.IsFocused附加属性实现绑定属性控制焦点的变化。
附加属性的代码如下:
public static class FocusChangeExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
} public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
} public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusChangeExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged)); private static void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = (UIElement)d;
if ((bool)e.NewValue)
{
control.Focus();
}
}
}
查看代码
五、VM+后台代码混合实现焦点控制及内容复制粘贴
1、后台代码主要实现复制粘贴内容,另外←→移动光标也需要后台代码控制。通过PreviewKeyDown事件捕获键盘左移右移,复制,删除等事件,做出相应处理:
private void Part2_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Back && part2.Text == "")
{
part1.Focus();
}
if (e.Key == Key.Right && part2.CaretIndex == part2.Text.Length)
{
part3.Focus();
e.Handled = true;
}
if (e.Key == Key.Left && part2.CaretIndex == )
{
part1.Focus();
e.Handled = true;
} if (e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Control) && e.Key == Key.C)
{
if (part2.SelectionLength == )
{
var vm = this.DataContext as IpAddressViewModel;
Clipboard.SetText(vm.AddressText);
}
}
}
部分代码
通过DataObject.AddPastingHandler(part1, TextBox_Pasting)添加粘贴事件。使控件赋值。
2、通过ViewModel方式实现属性绑定通知,来控制焦点变化及内容赋值。
ViewModel类要实现绑定通知需要实现INotifyPropertyChanged接口中的方法。
我们新建一个IpAddressViewModel类继承INotifyPropertyChanged,代码如下:
public class IpAddressViewModel : INotifyPropertyChanged
{
public event EventHandler AddressChanged; public string AddressText
{
get { return $"{Part1??""}.{Part2??""}.{Part3??""}.{Part4??""}"; }
} private bool isPart1Focused; public bool IsPart1Focused
{
get { return isPart1Focused; }
set { isPart1Focused = value; OnPropertyChanged(); }
} private string part1; public string Part1
{
get { return part1; }
set
{
part1 = value;
SetFocus(true, false, false, false); var moveNext = CanMoveNext(ref part1); OnPropertyChanged();
OnPropertyChanged(nameof(AddressText));
AddressChanged?.Invoke(this, EventArgs.Empty); if (moveNext)
{
SetFocus(false, true, false, false);
}
}
} private bool isPart2Focused; public bool IsPart2Focused
{
get { return isPart2Focused; }
set { isPart2Focused = value; OnPropertyChanged(); }
} private string part2; public string Part2
{
get { return part2; }
set
{
part2 = value;
SetFocus(false, true, false, false); var moveNext = CanMoveNext(ref part2); OnPropertyChanged();
OnPropertyChanged(nameof(AddressText));
AddressChanged?.Invoke(this, EventArgs.Empty); if (moveNext)
{
SetFocus(false, false, true, false);
}
}
} private bool isPart3Focused; public bool IsPart3Focused
{
get { return isPart3Focused; }
set { isPart3Focused = value; OnPropertyChanged(); }
} private string part3; public string Part3
{
get { return part3; }
set
{
part3 = value;
SetFocus(false, false, true, false);
var moveNext = CanMoveNext(ref part3); OnPropertyChanged();
OnPropertyChanged(nameof(AddressText));
AddressChanged?.Invoke(this, EventArgs.Empty); if (moveNext)
{
SetFocus(false, false, false, true);
}
}
} private bool isPart4Focused; public bool IsPart4Focused
{
get { return isPart4Focused; }
set { isPart4Focused = value; OnPropertyChanged(); }
} private string part4; public string Part4
{
get { return part4; }
set
{
part4 = value;
SetFocus(false, false, false, true);
var moveNext = CanMoveNext(ref part4); OnPropertyChanged();
OnPropertyChanged(nameof(AddressText));
AddressChanged?.Invoke(this, EventArgs.Empty); }
} public void SetAddress(string address)
{
if (string.IsNullOrWhiteSpace(address))
return; var parts = address.Split('.'); if (int.TryParse(parts[], out var num0))
{
Part1 = num0.ToString();
} if (int.TryParse(parts[], out var num1))
{
Part2 = parts[];
} if (int.TryParse(parts[], out var num2))
{
Part3 = parts[];
} if (int.TryParse(parts[], out var num3))
{
Part4 = parts[];
} } private bool CanMoveNext(ref string part)
{
bool moveNext = false; if (!string.IsNullOrWhiteSpace(part))
{
if (part.Length >= )
{
moveNext = true;
} if (part.EndsWith("."))
{
moveNext = true;
part = part.Replace(".", "");
}
} return moveNext;
} private void SetFocus(bool part1, bool part2, bool part3, bool part4)
{
IsPart1Focused = part1;
IsPart2Focused = part2;
IsPart3Focused = part3;
IsPart4Focused = part4;
} public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
查看代码
到这里基本就完成了,生成控件然后到MainWindow中引用该控件
六、最终效果

————————————————————
代码地址:https://github.com/cmfGit/IpAddressControl.git
WPF IP地址输入控件的实现的更多相关文章
- Delphi来实现一个IP地址输入控件
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...
- 获取Mac、CPUID、硬盘序列号、本地IP地址、外网IP地址OCX控件
提供获取Mac.CPUID.硬盘序列号.本地IP地址.外网IP地址OCX控件 开发语言:vc++ 可应用与WEB程序开发应用 <HTML><HEAD><TITLE> ...
- 正则表达式——WPF输入控件TextBox 限定输入特定字符
概念: 正则表达式是对字符串操作的一种逻辑公式, 就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个“规则字符串”, 这个“规则字符串”用来表达对字符串的一种过滤逻辑. 目的: 给定一个正 ...
- CYQ.Data 支持WPF相关的数据控件绑定(2013-08-09)
事件的结果 经过多天的思考及忙碌的开发及测试,CYQ.Data 终于在UI上全面支持WPF,至此,CYQ.Data 已经可以方便支持wpf的开发,同时,框架仍保留最低.net framework2.0 ...
- Android 高仿微信支付密码输入控件
像微信支付密码控件,在app中是一个多么司空见惯的功能.最近,项目需要这个功能,于是乎就实现这个功能. 老样子,投篮需要找准角度,变成需要理清思路.对于这个"小而美"的控件,我们思 ...
- WPF Step By Step 控件介绍
WPF Step By Step 控件介绍 回顾 上一篇,我们主要讨论了WPF的几个重点的基本知识的介绍,本篇,我们将会简单的介绍几个基本控件的简单用法,本文会举几个项目中的具体的例子,结合这些 例子 ...
- WPF中的ControlTemplate(控件模板)(转)
原文地址 http://www.cnblogs.com/zhouyinhui/archive/2007/03/28/690993.html WPF中的ControlTemplate(控件模板) ...
- CYQ.Data 支持WPF相关的数据控件绑定.Net获取iis版本
CYQ.Data 支持WPF相关的数据控件绑定(2013-08-09) 事件的结果 经过多天的思考及忙碌的开发及测试,CYQ.Data 终于在UI上全面支持WPF,至此,CYQ.Data 已经可以方便 ...
- 电子邮件和URL输入控件
HTML5还引入了让用户输入邮箱地址和URL的输入控件.那些不支持这类输入控件的浏览器会把他们当成普通文本框来处理. <!DOCTYPE html> <!-- To change t ...
随机推荐
- 洛谷P3603 || bzoj 4763 雪辉 && bzoj4812: [Ynoi2017]由乃打扑克
https://www.luogu.org/problemnew/show/P3603 https://www.lydsy.com/JudgeOnline/problem.php?id=4763 就是 ...
- C/S 和 B/S 架构
浏览器/服务器结构.它是C/S架构的一种改进,可以说属于三层C/S架构. 比较大的差别1.结构 C/S是两层架构,由客户端和服务器组成,而B/S是三层架构,由浏览器,WEB服务器和数据库服务器组成. ...
- MFC中的模态对话框与非模态对话框
模态对话框创建: MyDialog mydlg; mydlg.DoModal() 当前只能运行此模态对话框,且停止主窗口的运行,直到模态对话框退出,才允许主窗口运行. 模态对话框的关闭顺序: OnCl ...
- JAVA Debug调试技术
System.out.println(e),这个方法打印出异常,并且输出在哪里出现的异常,不过它和另外一个e.printStackTrace()方法不同.后者也是打印出异常,但是它还将显示出更深的调用 ...
- JAVA常用知识总结(十三)——数据库(三)
Mysql的主键选择(主键自增,UUID,snowflake)? 使用自增长做主键的优点:1.很小的数据存储空间2.性能最好3.容易记忆使用自增长做主键的缺点:1.如果存在大量的数据,可能会超出自增长 ...
- python学习之结构语句
一 循环语句: 1.1 for x in rang(n) :#rang(n)生成左闭右开区间的序列 1.2 while x 条件n: 二条件语句: if 条件表达式: elif 表达式: elif 表 ...
- 自己项目中PHP常用工具类大全分享
<?php /** * 助手类 * @author www.shouce.ren * */ class Helper { /** * 判断当前服务器系统 * @return string */ ...
- yii2 使用gii生成代码文件
访问地址: http://localhost/yii2-test/web/index.php?r=gii 如果你通过本机以外的机器访问 Gii,请求会被出于安全原因拒绝. 在web.php修改gii ...
- 基于.NET网页开发的工作,需要掌握的知识点
学习计划对于程序员来说尤为重要,我最近根据自己的职业规划和招聘网站上对于基于.NET网页开发工作所需要的技能做出了一个总结,这个总结的内容也将是自己最近一年的知识补充和学习的方向,各位园友也可以把它作 ...
- C#入门笔记1
C#是用于创建要运行在.NET CLR上的应用程序的语言之一,从C和C++语言演化而来,是微软专门为使用.NET平台而创建的.优点:就是彻头彻尾为.NET Framework设计语言. C#能编写什么 ...