WPF自定义实现IP地址输入控件
作者:一叶知秋,知寒冬 发布时间:2022-02-24 05:35:36
一、前言
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 = 0;
var strVal = (string)value;
try
{
if (strVal.Length > 0)
{
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 == 0)
{
part1.Focus();
e.Handled = true;
}
if (e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Control) && e.Key == Key.C)
{
if (part2.SelectionLength == 0)
{
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??"0"}.{Part2??"0"}.{Part3??"0"}.{Part4??"0"}"; }
}
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[0], out var num0))
{
Part1 = num0.ToString();
}
if (int.TryParse(parts[1], out var num1))
{
Part2 = parts[1];
}
if (int.TryParse(parts[2], out var num2))
{
Part3 = parts[2];
}
if (int.TryParse(parts[3], out var num3))
{
Part4 = parts[3];
}
}
private bool CanMoveNext(ref string part)
{
bool moveNext = false;
if (!string.IsNullOrWhiteSpace(part))
{
if (part.Length >= 3)
{
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
来源:https://www.cnblogs.com/xiaomingg/p/10962642.html


猜你喜欢
- Java获取控制台输入的方法在学习网络编程中,有需要从控制台输入数据,进行两个线程之间的通信,其中,涉及到了读取控制台输入的两种不同的操作,
- 目录之前的处理方式现在的处理方式其他场景处理重复点击间接设置点击富文本列表数据绑定总结项目地址一般手机上的 Android App,主要的交
- C++虚类相当于java中的抽象类,与接口的不同之处是:1.一个子类只能继承一个抽象类(虚类),但能实现多个接口2.一个抽象类可以有构造方法
- 本文实例为大家分享了Unity Shader序列帧动画效果的具体代码,供大家参考,具体内容如下 实现原理主要的思想是设置显示UV
- * 什么是 * Spring MVC中的 * (Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户
- 在开发中,我们会遇到将不同组织架构合并成tree这种树状结构,那么如果做呢?实际上,我们也可以理解为如何将拥有父子关系的list转成树形结构
- 上一篇文章我们介绍了Apache Commons Math3学习之数值积分实例代码,这里给大家分享math3多项式曲线拟合的相关内容,具体如
- bootstrap和application的区别说明bootstrap和application都是SpringBoot项目中的配置文件,他们
- 在本篇博文中,我们主要讲解一下 IntelliJ IDEA 安装目录中的一些核心文件的功能及用法:如上图所示,我们定位到了 IntelliJ
- C++/java 继承类的多态详解学过C++和Java的人都知道,他们二者由于都可以进行面向对象编程,而面向对象编程的三大特性就是封装、继承
- 开篇我们还是和原来一样,讲一讲做爬虫的思路以及需要准备的知识吧,高手们请直接忽略。首先我们来缕一缕思绪,想想到底要做什么,列个简单的需求。需
- 包括了写入和读取功能. 写入的时候, 如果文件不存在会自动创建. 如果对应的键已经存在, 则自动覆盖它的值. 读取的时候, 如果对应的文件不
- 网络编程TCP实现聊天的前提还需要掌握IO流,话不多说,直接上代码!客户端:package com.kuang.lesson02;impor
- Mybatis条件if test使用枚举值1.正确package com.weather.weatherexpert.common.util
- 面试题一:判断下列程序运行结果package String_test;public class test_1 { public static
- 什么是状态管理状态管理是一个十分广泛的概念,因为状态无处不在。从广义角度讲状态管理就是页面跟代码、跟服务器进行数据同步。例如:你在某购物应用
- 本文实例为大家分享了shader实现基于世界坐标的贴图置换效果。效果如下:设置面板如下:可在面板上设置切换方向,与切换对象,及其切换速度。s
- 一、闭包的定义。有很多不同的人都对闭包过进行了定义,这里收集了一些。# 是引用了自由变量的函数。这个函数通常被定义在另一个外部函数中,并且引
- 本文实例讲述了C# Console利用mspaint打开图像并保存的方法。分享给大家供大家参考,具体如下:调用画图板压缩图片System.D
- 一、概述在App中,经常会出现侧滑菜单,侧滑滑出View等效果,虽然说Android有很多第三方开源库,但是实际上咱们可以自己也写一个自定义