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 ...
随机推荐
- 洛谷P1823 [COI2007] Patrik 音乐会的等待
https://www.luogu.org/problemnew/show/P1823 自己只会一个log的 设取的人的位置分别是l,r(l<r) 这个做法大概是考虑枚举r,设法对于每个r求出有 ...
- 记录一下filter
filter是什么,如它的字面意思,就是拦截器.它可以在request到达相关资源之前,比如servlet之前先处理requeset,也可以拦截或处理从某个资源比如servlet发出的response ...
- Springboot的static和templates
static和templates部分参考博客:https://blog.csdn.net/wangb_java/article/details/71775637 热部署参考博客:https://www ...
- 玲珑杯”ACM比赛 Round #4 1054 - String cut 暴力。学到了扫描的另一种思想
http://www.ifrog.cc/acm/problem/1054 问删除一个字符后的最小循环节是多少. 比赛的时候想不出,不知道怎么暴力. 赛后看了别人代码才晓得.唉,还以为自己字符串还不错, ...
- python学习day11
目录 SqlAlchemy 外键 SqlAlechemy SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象 ...
- Storm编程入门API系列之Storm的Topology默认Workers、默认executors和默认tasks数目
关于,storm的启动我这里不多说了. 见博客 storm的3节点集群详细启动步骤(非HA和HA)(图文详解) 建立stormDemo项目 Group Id : zhouls.bigdata Art ...
- WGET and CURL
目录 WGET and CURL 对比 wget curl curl使用示例 WGET and CURL 对比 CURL 和WGET都可以用来下载文件,用法也类似:curl/wget [-option ...
- c#操作ecxel的一些资源(downmoon搜集)
c#操作ecxel的一些资源(downmoon搜集) 工作需要,邀月收集了几个操作excel的资源. 1.如何:使用 COM Interop 创建 Excel 电子表格(C# 编程指南)http:/ ...
- java 并发容器一之ConcurrentHashMap(基于JDK1.8)
上一篇文章简单的写了一下,BoundedConcurrentHashMap,觉得https://www.cnblogs.com/qiaoyutao/p/10903813.html用的并不多:今天着重写 ...
- IOS之网络状态设和NSUserDefaults的synchronize
#pragma mark - check net status int apiCheckNetStatus() { Reachability *reachNet = [Reachability rea ...