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比较问题详细分析

    2023-11-20 14:30:48
  • JAVA随机打乱数组顺序的方法

    2023-08-11 18:56:03
  • Java中\\n和\\r区别

    2023-01-16 10:38:53
  • java实现静默安装apk

    2023-08-31 08:11:34
  • c#测试本机sql运算速度的代码示例分享

    2022-10-18 00:04:24
  • Map与JavaBean相互转换的工具类 

    2021-09-22 20:59:02
  • 浅谈Java中实现深拷贝的两种方式—clone() & Serialized

    2022-04-05 18:36:15
  • Swing拆分窗格控件JSplitPane使用详解

    2022-11-14 21:13:48
  • Android RecyclerView 实现快速滚动的示例代码

    2023-02-17 05:32:50
  • unity使用射线实现贴花系统

    2023-04-23 10:22:00
  • Android开发实战闹钟项目

    2022-02-28 19:48:38
  • Android自定义view实现仿抖音点赞效果

    2021-11-04 11:58:19
  • 冒泡排序算法原理及JAVA实现代码

    2022-08-13 10:30:40
  • idea 如何查找类中的某个方法

    2022-03-17 17:17:42
  • 使用C++程序获取新浪行情数据的方法

    2022-06-16 13:14:35
  • 关于Android的 DiskLruCache磁盘缓存机制原理

    2022-12-16 16:57:43
  • 关于在IDEA中SpringBoot项目中activiti工作流的使用详解

    2022-12-11 01:24:28
  • Java实现中国象棋的示例代码

    2021-08-10 21:56:03
  • Android 自定义Switch开关按钮的样式实例详解

    2023-09-09 16:38:39
  • 简单记事本java源码实例

    2023-11-26 02:03:17
  • asp之家 软件编程 m.aspxhome.com