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 ...
随机推荐
- SpirngMVC-JSON
Springmvc默认用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包,如下: 配置json转换器 在注解适配器中加入messa ...
- datagridview连接数据库的简单实现
对于需要从数据库获取数据的列,在DataPropertyName填写对应的列名字. 在Load函数中添加如下代码,先关闭自动生成列选项,然后得到一个查询的datatable,并将其绑定到datasou ...
- SSH之小问题解惑
(注:以下版本指的是spring3+hibernate3+struts2) 1,web开发中,servlet对象是否线程安全? 当一个http请求到来时,web容器的调度线程(Dispach Thre ...
- Unity Shader入门精要学习笔记 - 第16章 Unity中的渲染优化技术
转自冯乐乐的 <Unity Shader 入门精要> 移动平台的特点 为了尽可能一处那些隐藏的表面,减少overdraw(即一个像素被绘制多次),PowerVR芯片(通常用于ios设备和某 ...
- asp。Net 页面传值
00.引言 Web页面是无状态的, 服务器对每一次请求都认为来自不同用户,因此,变量的状态在连续对同一页面的多次请求之间或在页面跳转时不会被保留.在用ASP.NET 设计开发一个Web系统时, 遇到一 ...
- SpringBoot学习笔记-Chapter2(hello word)
开篇 第一次在博客园上写博客,初衷是想记录一下学习笔记,以往都是用笔去记录下学习笔记,现在来看在效率.检索速度上以及可可复制性都不好.作为一名Java开发人员 不会Spring Boot一定会被鄙视的 ...
- sublime text less安装踩坑图文讲解(less无法生成css)
唉,怎么感觉做个前端几乎把所有的坑都踩遍了啊,别人按照网上安装了一遍就好使,我这里就死活不行. 先说一下我的问题:网上说的能安装的都按了,可是sublime就是不给我生成css文件,后来知道了,就是l ...
- AzureARM 使用 powershell 扩容系统磁盘大小
azure中的虚拟机,windows磁盘大小为127G,linux磁盘大小为30G,在很多时候部署应用程序时直接部署到系统磁盘内导致磁盘后期容量不够需要扩容,在执行分区扩容前我们需要先通过Potal或 ...
- linux服务器上的jenkins远程触发构建windows server 2012服务器上的jenkins任务
本文来自:https://blog.csdn.net/huashao0602/article/details/53318295 非常感谢原博主,亲测可行,这是我做CI持续集成试过的第6套方案了! 背 ...
- oracle数据比对工具
上半年的工作重心主要是机房搬迁,免不了要经常的数据比对,保证主备库数据一致,为了节约工作时间,提高工作效率,开发了这个数据比对小工具.用起来还可以.有需要的QQ私聊(1603039990),方便大家, ...