李sir_Blog

博客园 首页 联系 订阅 管理
  705 随笔 :: 58 文章 :: 134 评论 :: 193万 阅读

一、前言

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

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

  • 输满三个数字焦点会往右移
  • 键盘←→可以空光标移动
  • 任意位置可复制整段IP地址,且支持x.x.x.x格式的粘贴赋值
  • 删除字符会自动向左移动焦点

知道以上特性,我们就可以开始动手了。

二、构成

Grid+TextBox*4+TextBlock*3

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

界面代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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             <ColumnDefinition Width="10" />
 44             <ColumnDefinition MinWidth="30" />
 45             <ColumnDefinition Width="10" />
 46             <ColumnDefinition MinWidth="30" />
 47             <ColumnDefinition Width="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

 

posted on   李sir  阅读(262)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示