WPF自定义实现IP地址输入控件

作者:一叶知秋,知寒冬 时间:2022-02-24 05:35:36 

一、前言

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

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

WPF自定义实现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 = 0;
 var strVal = (string)value;
 try
 {
  if (strVal.Length > 0)
  {
   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 == 0)
{
 part1.Focus();
 e.Handled = true;
}

if (e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Control) && e.Key == Key.C)
{
 if (part2.SelectionLength == 0)
 {
  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??"0"}.{Part2??"0"}.{Part3??"0"}.{Part4??"0"}"; }
}

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[0], out var num0))
 {
  Part1 = num0.ToString();
 }

if (int.TryParse(parts[1], out var num1))
 {
  Part2 = parts[1];
 }

if (int.TryParse(parts[2], out var num2))
 {
  Part3 = parts[2];
 }

if (int.TryParse(parts[3], out var num3))
 {
  Part4 = parts[3];
 }

}

private bool CanMoveNext(ref string part)
{
 bool moveNext = false;

if (!string.IsNullOrWhiteSpace(part))
 {
  if (part.Length >= 3)
  {
   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中引用该控件

六、最终效果

WPF自定义实现IP地址输入控件

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

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

来源:https://www.cnblogs.com/xiaomingg/p/10962642.html

标签:wpf,ip地址,控件
0
投稿

猜你喜欢

  • Java面试题冲刺第十六天--消息队列

    2022-08-08 09:07:04
  • Java基于余弦方法实现的计算相似度算法示例

    2022-06-29 22:02:13
  • Mybatis-Plus自动填充更新操作相关字段的实现

    2023-06-04 22:37:12
  • 使用Maven搭建Hadoop开发环境

    2021-09-11 07:55:45
  • 详解Spring框架入门

    2023-08-14 12:56:14
  • 替换so文件来动态替换Flutter代码实现详解

    2023-06-23 16:24:06
  • java实现word文档转pdf并添加水印的方法详解

    2023-05-26 02:59:18
  • Java泛型的使用限制实例分析

    2023-05-07 20:14:52
  • SpringBoot之通过BeanPostProcessor动态注入ID生成器案例详解

    2023-11-24 22:17:26
  • JavaWeb实现文件上传下载功能实例详解

    2023-05-08 19:43:51
  • Java多线程通信:交替打印ABAB实例

    2022-04-08 06:57:28
  • SpringData JPA中@OneToMany和@ManyToOne的用法详解

    2021-10-01 00:49:10
  • Java WebService 简单实例(附实例代码)

    2023-01-25 07:52:18
  • 使用Java8 Stream流的skip + limit实现批处理的方法

    2023-11-29 06:17:39
  • HttpServletRequest对象常用功能_动力节点Java学院整理

    2022-01-05 10:37:08
  • Java如何实现简单后台访问并获取IP

    2021-10-27 02:26:27
  • Android 消息机制以及handler的内存泄露

    2023-08-01 07:59:44
  • C#策略模式(Strategy Pattern)实例教程

    2022-11-29 07:35:07
  • C#中动态数组用法实例

    2021-11-30 16:42:23
  • SVN报错:Error Updating changes:svn:E155037的解决方案

    2023-06-11 07:27:11
  • asp之家 软件编程 m.aspxhome.com