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 ...
随机推荐
- ERP实施顾问,请找准自己的定位
最近给一些实施顾问做了培训.这些实施顾问都是我们渠道伙伴中具有较高提升潜质的顾问,期待做一次集中培训,他们能够在ERP项目实施上有所突破与提升,并能够为公司的ERP项目实施工作承担更多职责,分担更多压 ...
- c指针参数常见错误
参数的地址是可以修改的,修改后的地址是不可能传回给调用处的指针变量.也就是说,可以修改参数地址所指的单元的值,这是可以传回到调用处的变量里面的. #include <stdio.h> #i ...
- shell脚本解析json文件
安装jq扩展 下载:jq 根据自己系统下载对应的文件 cp jq-linux64 /usr/bin cd /usr/bin mv jq-linux64 jq chmod +x jq 使用方法 假设有个 ...
- Objective-C Data Encapsulation
All Objective-C programs are composed of the following two fundamental elements: Program statements ...
- Java 语言中一个字符占几个字节?
Java中理论说是一个字符(汉字 字母)占用两个字节. 但是在UTF-8的时候 new String("字").getBytes().length 返回的是3 表示3个字节 作者: ...
- haml scss转换编写html css的前期工作
http://www.w3cplus.com/sassguide/install.html 先下载ruby $ gem sources $ gem sources --remove https://r ...
- 【GIMP学习】抠图方法二则
之前抠图都比较二,懒人我尝试过在线抠图软件.以及在线PS简易版,真的都很不好用,前者简单粗暴,后者我遇到各种储存不能的bug. 在ubuntu的环境下有一个功能可以和PS相媲美的功能强大图片处理软件G ...
- UWP中获取Encoding.Default
Encoding.GetEncoding(0); 即可
- x+2y+3z=n非负整数解
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; ty ...
- urllib基础-利用网站结构爬取网页-百度搜索
有的时候爬取网页,可以利用网站额结构特点爬取网页 在百度搜索框中输入搜索内容,单击搜索,浏览器会发送一个带有参数的url请求.尝试删除其中的一些参数,只剩下wd这个参数.发现wd是搜索内容.这样程序可 ...