欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-09-10 08:45:45
一、前言 wpf没有内置ip地址输入控件,因此我们需要通过自己定义实现。 我们先看一下ip地址输入控件有什么特性: 输满三个数字焦点会往右移 键...

一、前言

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。