• WPF IP地址输入控件的实现


    一、前言

    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             <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

  • 相关阅读:
    连接mysql慢或者多台服务器ping不通mysql
    java8 Stream
    Idea 提示xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
    sublime Text的使用
    BigDecimal 加减乘除 比较大小 setScale(精度处理)
    kubectl 命令
    mac brew命令的使用
    mysql json
    idea open打开项目之后,project里没有目录结构。
    mac 多显示器焦点快速切换
  • 原文地址:https://www.cnblogs.com/xiaomingg/p/10962642.html
Copyright © 2020-2023  润新知