一、前言

WPF没有内置IP地址输入控件,因此我们需要通过自己定义实现。

我们先看一下IP地址输入控件有什么特性:



* 输满三个数字焦点会往右移
* 键盘←→可以空光标移动
* 任意位置可复制整段IP地址,且支持x.x.x.x格式的粘贴赋值
* 删除字符会自动向左移动焦点
知道以上特性,我们就可以开始动手了。

二、构成

Grid+TextBox*4+TextBlock*3

通过这几个控件的组合,我们完成IP地址输入控件的功能。

界面代码如下:
1 <UserControl 2 x:Class="IpAddressControl.IpAddressControl" 3 xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:d
="http://schemas.microsoft.com/expression/blend/2008" 6 xmlns:local
="clr-namespace:IpAddressControl" 7 xmlns:mc
="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 Margin="10,0"
9 d:DesignHeight="50" 10 d:DesignWidth="800" 11 mc:Ignorable="d"
Background="White"> 12 <UserControl.Resources> 13 <ControlTemplate x:Key
="validationTemplate"> 14 <DockPanel> 15 <TextBlock 16 Margin="1,2" 17
DockPanel.Dock="Right" 18 FontSize="{DynamicResource ResourceKey=Heading4}"
19 FontWeight="Bold" 20 Foreground="Red" 21 Text="" /> 22 <
AdornedElementPlaceholder/> 23 </DockPanel> 24 </ControlTemplate> 25 <Style
x:Key="CustomTextBoxTextStyle" TargetType="TextBox"> 26 <Setter Property
="MaxLength" Value="3" /> 27 <Setter Property="HorizontalAlignment" Value
="Stretch" /> 28 <Setter Property="VerticalAlignment" Value="Center" /> 29 <
Style.Triggers> 30 <Trigger Property="Validation.HasError" Value="True"> 31 <
Trigger.Setters> 32 <Setter Property="ToolTip" Value="{Binding
RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"
/> 33 <Setter Property="BorderBrush" Value="Red" /> 34 <Setter Property
="Background" Value="Red" /> 35 </Trigger.Setters> 36 </Trigger> 37 </
Style.Triggers> 38 </Style> 39 </UserControl.Resources> 40 <Grid> 41 <
Grid.ColumnDefinitions> 42 <ColumnDefinition MinWidth="30" /> 43 <
ColumnDefinitionWidth="10" /> 44 <ColumnDefinition MinWidth="30" /> 45 <
ColumnDefinitionWidth="10" /> 46 <ColumnDefinition MinWidth="30" /> 47 <
ColumnDefinitionWidth="10" /> 48 <ColumnDefinition MinWidth="30" /> 49 </
Grid.ColumnDefinitions> 50 51 <!-- Part 1 --> 52 <TextBox 53 Grid.Column="0"
54 BorderThickness="0" 55 HorizontalAlignment="Stretch" 56
VerticalAlignment="Stretch" 57 VerticalContentAlignment="Center" 58
HorizontalContentAlignment="Center" 59 x:Name="part1" 60 PreviewKeyDown
="Part1_PreviewKeyDown" 61 local:FocusChangeExtension.IsFocused="{Binding
IsPart1Focused, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
NotifyOnSourceUpdated=True}" 62 Style="{StaticResource CustomTextBoxTextStyle}
" 63 Validation.ErrorTemplate="{StaticResource validationTemplate}"> 64 <
TextBox.Text> 65 <Binding Path="Part1" UpdateSourceTrigger="PropertyChanged">
66 <Binding.ValidationRules> 67 <local:IPRangeValidationRule Max="255" Min="0"
/> 68 </Binding.ValidationRules> 69 </Binding> 70 </TextBox.Text> 71 </
TextBox> 72 <TextBlock 73 Grid.Column="1" 74 HorizontalAlignment="Center"
75 FontSize="15" 76 Text="." 77 VerticalAlignment="Center" 78 /> 79 80
<!-- Part 2 --> 81 <TextBox 82 Grid.Column="2" 83 x:Name="part2" 84
BorderThickness="0" 85 VerticalAlignment="Stretch" 86
VerticalContentAlignment="Center" 87 HorizontalContentAlignment="Center" 88
PreviewKeyDown="Part2_KeyDown" 89 local:FocusChangeExtension.IsFocused="
{Binding IsPart2Focused}" 90 Style="{StaticResource CustomTextBoxTextStyle}"
91 Validation.ErrorTemplate="{StaticResource validationTemplate}"> 92 <
TextBox.Text> 93 <Binding Path="Part2" UpdateSourceTrigger="PropertyChanged">
94 <Binding.ValidationRules> 95 <local:IPRangeValidationRule Max="255" Min="0"
/> 96 </Binding.ValidationRules> 97 </Binding> 98 </TextBox.Text> 99 </
TextBox> 100 <TextBlock 101 Grid.Column="3" 102 HorizontalAlignment="Center"
103 FontSize="15" 104 Text="." 105 VerticalAlignment="Center"/> 106 107 <!--
Part 3--> 108 <TextBox 109 Grid.Column="4" 110 x:Name="part3" 111
BorderThickness="0" 112 VerticalAlignment="Stretch" 113
VerticalContentAlignment="Center" 114 HorizontalContentAlignment="Center" 115
PreviewKeyDown="Part3_KeyDown" 116 local:FocusChangeExtension.IsFocused="
{Binding IsPart3Focused}" 117 Style="{StaticResource CustomTextBoxTextStyle}"
118 Validation.ErrorTemplate="{StaticResource validationTemplate}"> 119 <
TextBox.Text> 120 <Binding Path="Part3" UpdateSourceTrigger="PropertyChanged">
121 <Binding.ValidationRules> 122 <local:IPRangeValidationRule Max="255" Min="0"
/> 123 </Binding.ValidationRules> 124 </Binding> 125 </TextBox.Text> 126 </
TextBox> 127 <TextBlock 128 Grid.Column="5" 129 HorizontalAlignment="Center"
130 FontSize="15" 131 Text="." 132 VerticalAlignment="Center"/> 133 134 <!--
Part 4--> 135 <TextBox 136 Grid.Column="6" 137 x:Name="part4" 138
BorderThickness="0" 139 VerticalAlignment="Stretch" 140
VerticalContentAlignment="Center" 141 HorizontalContentAlignment="Center" 142
PreviewKeyDown="Part4_KeyDown" 143 local:FocusChangeExtension.IsFocused="
{Binding IsPart4Focused}" 144 Style="{StaticResource CustomTextBoxTextStyle}"
145 Validation.ErrorTemplate="{StaticResource validationTemplate}"> 146 <
TextBox.Text> 147 <Binding Path="Part4" UpdateSourceTrigger="PropertyChanged">
148 <Binding.ValidationRules> 149 <local:IPRangeValidationRule Max="255" Min="0"
/> 150 </Binding.ValidationRules> 151 </Binding> 152 </TextBox.Text> 153 </
TextBox> 154 </Grid> 155 </UserControl> 查看代码
三、验证输入格式

界面中为TextBox添加了CustomTextBoxTextStyle及validationTemplate样式,当输入格式不正确时,控件就会应用该样式。

通过自定义规则IPRangeValidationRule来验证输入的内容格式是否要求。

自定义规则代码如下:
1 public class IPRangeValidationRule : ValidationRule 2 { 3 private int
_min; 4 private int _max; 5 6 public int Min 7 { 8 get { return _min; } 9
set { _min = value; } 10 } 11 12 public int Max 13 { 14 get { return _max; }
15 set { _max = value; } 16 } 17 18 public override ValidationResult Validate(
object value, CultureInfo cultureInfo) 19 { 20 int val = 0; 21 var strVal = (
string)value; 22 try 23 { 24 if (strVal.Length > 0) 25 { 26 if
(strVal.EndsWith(".")) 27 { 28 return CheckRanges(strVal.Replace(".", "")); 29
}30 31 // Allow dot character to move to next box 32 return
CheckRanges(strVal);33 } 34 } 35 catch (Exception e) 36 { 37 return new
ValidationResult(false, "Illegal characters or " + e.Message); 38 } 39 40 if
((val < Min) || (val > Max)) 41 { 42 return new ValidationResult(false, 43 "
Please enter the value in the range:" + Min + " - " + Max + "."); 44 } 45 else
46 { 47 return ValidationResult.ValidResult; 48 } 49 } 50 51 private
ValidationResult CheckRanges(string strVal) 52 { 53 if (int.TryParse(strVal,
out var res)) 54 { 55 if ((res < Min) || (res > Max)) 56 { 57 return new
ValidationResult(false, 58 "Please enter the value in the range: " + Min + " - "
+ Max +"."); 59 } 60 else 61 { 62 return ValidationResult.ValidResult; 63 }
64 } 65 else 66 { 67 return new ValidationResult(false, "Illegal characters
entered"); 68 } 69 } 70 } 查看代码
四、控制焦点变化

在界面代码中我通过local:FocusChangeExtension.IsFocused附加属性实现绑定属性控制焦点的变化。

附加属性的代码如下:
1 public static class FocusChangeExtension 2 { 3 public static bool
GetIsFocused(DependencyObject obj) 4 { 5 return (bool
)obj.GetValue(IsFocusedProperty); 6 } 7 8 public static void
SetIsFocused(DependencyObject obj,bool value) 9 { 10
obj.SetValue(IsFocusedProperty, value);11 } 12 13 public static readonly
DependencyProperty IsFocusedProperty =14 DependencyProperty.RegisterAttached(
15 "IsFocused", typeof(bool), typeof(FocusChangeExtension), 16 new
UIPropertyMetadata(false, OnIsFocusedPropertyChanged)); 17 18 private static
void OnIsFocusedPropertyChanged( 19 DependencyObject d, 20
DependencyPropertyChangedEventArgs e)21 { 22 var control = (UIElement)d; 23 if
((bool)e.NewValue) 24 { 25 control.Focus(); 26 } 27 } 28 } 查看代码
五、VM+后台代码混合实现焦点控制及内容复制粘贴

1、后台代码主要实现复制粘贴内容,另外←→移动光标也需要后台代码控制。通过PreviewKeyDown事件捕获键盘左移右移,复制,删除等事件,做出相应处理:
1 private void Part2_KeyDown(object sender, System.Windows.Input.KeyEventArgs
e) 2 { 3 if (e.Key == Key.Back && part2.Text == "") 4 { 5 part1.Focus(); 6
} 7 if (e.Key == Key.Right && part2.CaretIndex == part2.Text.Length) 8 { 9
part3.Focus();10 e.Handled = true; 11 } 12 if (e.Key == Key.Left &&
part2.CaretIndex ==0) 13 { 14 part1.Focus(); 15 e.Handled = true; 16 } 17 18
if (e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Control) && e.Key == Key.C)
19 { 20 if (part2.SelectionLength == 0) 21 { 22 var vm = this.DataContext as
IpAddressViewModel;23 Clipboard.SetText(vm.AddressText); 24 } 25 } 26 } 部分代码
通过DataObject.AddPastingHandler(part1, TextBox_Pasting)添加粘贴事件。使控件赋值。

2、通过ViewModel方式实现属性绑定通知,来控制焦点变化及内容赋值。

ViewModel类要实现绑定通知需要实现INotifyPropertyChanged接口中的方法。

我们新建一个IpAddressViewModel类继承INotifyPropertyChanged,代码如下:
1 public class IpAddressViewModel : INotifyPropertyChanged 2 { 3 public
event EventHandler AddressChanged; 4 5 public string AddressText 6 { 7 get
{return $"{Part1??"0"}.{Part2??"0"}.{Part3??"0"}.{Part4??"0"}"; } 8 } 9 10
private bool isPart1Focused; 11 12 public bool IsPart1Focused 13 { 14 get {
return isPart1Focused; } 15 set { isPart1Focused = value; OnPropertyChanged();
} 16 } 17 18 private string part1; 19 20 public string Part1 21 { 22 get
{return part1; } 23 set 24 { 25 part1 = value; 26 SetFocus(true, false,
false, false); 27 28 var moveNext = CanMoveNext(ref part1); 29 30
OnPropertyChanged(); 31 OnPropertyChanged(nameof(AddressText)); 32
AddressChanged?.Invoke(this, EventArgs.Empty); 33 34 if (moveNext) 35 { 36
SetFocus(false, true, false, false); 37 } 38 } 39 } 40 41 private bool
isPart2Focused; 42 43 public bool IsPart2Focused 44 { 45 get { return
isPart2Focused; } 46 set { isPart2Focused = value; OnPropertyChanged(); } 47 }
48 49 50 private string part2; 51 52 public string Part2 53 { 54 get {
return part2; } 55 set 56 { 57 part2 = value; 58 SetFocus(false, true,
false, false); 59 60 var moveNext = CanMoveNext(ref part2); 61 62
OnPropertyChanged(); 63 OnPropertyChanged(nameof(AddressText)); 64
AddressChanged?.Invoke(this, EventArgs.Empty); 65 66 if (moveNext) 67 { 68
SetFocus(false, false, true, false); 69 } 70 } 71 } 72 73 private bool
isPart3Focused; 74 75 public bool IsPart3Focused 76 { 77 get { return
isPart3Focused; } 78 set { isPart3Focused = value; OnPropertyChanged(); } 79 }
80 81 private string part3; 82 83 public string Part3 84 { 85 get {
return part3; } 86 set 87 { 88 part3 = value; 89 SetFocus(false, false,
true, false); 90 var moveNext = CanMoveNext(ref part3); 91 92
OnPropertyChanged(); 93 OnPropertyChanged(nameof(AddressText)); 94
AddressChanged?.Invoke(this, EventArgs.Empty); 95 96 if (moveNext) 97 { 98
SetFocus(false, false, false, true); 99 } 100 } 101 } 102 103 private bool
isPart4Focused;104 105 public bool IsPart4Focused 106 { 107 get { return
isPart4Focused; }108 set { isPart4Focused = value; OnPropertyChanged(); } 109 }
110 111 private string part4; 112 113 public string Part4 114 { 115 get {
return part4; } 116 set 117 { 118 part4 = value; 119 SetFocus(false, false,
false, true); 120 var moveNext = CanMoveNext(ref part4); 121 122
OnPropertyChanged();123 OnPropertyChanged(nameof(AddressText)); 124
AddressChanged?.Invoke(this, EventArgs.Empty); 125 126 } 127 } 128 129 public
void SetAddress(string address) 130 { 131 if (string
.IsNullOrWhiteSpace(address))132 return; 133 134 var parts = address.Split('.');
135 136 if (int.TryParse(parts[0], out var num0)) 137 { 138 Part1 =
num0.ToString();139 } 140 141 if (int.TryParse(parts[1], out var num1)) 142 {
143 Part2 = parts[1]; 144 } 145 146 if (int.TryParse(parts[2], out var num2))
147 { 148 Part3 = parts[2]; 149 } 150 151 if (int.TryParse(parts[3], out var
num3))152 { 153 Part4 = parts[3]; 154 } 155 156 } 157 158 private bool
CanMoveNext(ref string part) 159 { 160 bool moveNext = false; 161 162 if (!
string.IsNullOrWhiteSpace(part)) 163 { 164 if (part.Length >= 3) 165 { 166
moveNext =true; 167 } 168 169 if (part.EndsWith(".")) 170 { 171 moveNext =
true; 172 part = part.Replace(".", ""); 173 } 174 } 175 176 return moveNext;
177 } 178 179 private void SetFocus(bool part1, bool part2, bool part3, bool
part4)180 { 181 IsPart1Focused = part1; 182 IsPart2Focused = part2; 183
IsPart3Focused = part3; 184 IsPart4Focused = part4; 185 } 186 187 public event
PropertyChangedEventHandler PropertyChanged;188 189 190 protected virtual void
OnPropertyChanged([CallerMemberName]string propertyName = null) 191 { 192
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 193
}194 } 查看代码
到这里基本就完成了,生成控件然后到MainWindow中引用该控件

六、最终效果



————————————————————

代码地址:https://github.com/cmfGit/IpAddressControl.git
<https://github.com/cmfGit/IpAddressControl.git>

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信