数字输入框

简介

在业务中,我们经常需要限制用户的输入,比如限制输入长度,限制只能输入数字等等。限制输入长度WPF内置的TextBox已经帮我们解决了,但是限制输入数字却并未在WPF中内置解决方案。使用第三方的控件又要多增加一个引用,于是决定自己写一个。

在写的过程中发现需要考虑的问题比较多,比如限制输入法、部分限制输入小数点和负号、限制输入字母和其它符号、粘贴时做特殊处理等等。值得一提的是,将文本绑定到Double型且将UpdateSourceTrigger设为PropertyChanged时,出现了界面上包含小数点,但是通过Text获取的文本却并不包含小数点的情况,猜测原因是因为绑定更新和自动类型转换出现的问题。

数字输入框提供了设置最小值(可用)、最大值(可用)、精度(小数点后位数)的功能。不合法的按键将会被过滤掉,输入的值小于最小值或者大于最大值时,其输入都将视为无效。

代码

代码较简单,且有注释,不再多说。

namespace YiYan127.WPF.Controls
{
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; /// <summary>
/// 输入数值的文本框
/// </summary>
public class NumbericTextBox : TextBox
{
#region Fields #region DependencyProperty /// <summary>
/// 最大值的依赖属性
/// </summary>
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
"MaxValue",
typeof(double),
typeof(NumbericTextBox),
new PropertyMetadata(double.MaxValue)); /// <summary>
/// 最小值的依赖属性
/// </summary>
public static readonly DependencyProperty MinValueProperty = DependencyProperty.Register(
"MinValue",
typeof(double),
typeof(NumbericTextBox),
new PropertyMetadata(double.MinValue)); /// <summary>
/// 精度的依赖属性
/// </summary>
public static readonly DependencyProperty PrecisionProperty = DependencyProperty.Register(
"Precision",
typeof(ushort),
typeof(NumbericTextBox),
new PropertyMetadata((ushort)2)); #endregion DependencyProperty /// <summary>
/// 先前合法的文本
/// </summary>
private string lastLegalText; /// <summary>
/// 是否为粘贴
/// </summary>
private bool isPaste; public event EventHandler<TextChangedEventArgs> PreviewTextChanged; #endregion Fields #region Constructor /// <summary>
/// 构造函数
/// </summary>
public NumbericTextBox()
{
this.PreviewTextInput += this.NumbericTextBoxPreviewTextInput;
this.TextChanged += this.NumbericTextBoxTextChanged;
this.PreviewKeyDown += this.NumbericTextBox_PreviewKeyDown;
this.LostFocus += this.NumbericTextBoxLostFocus;
InputMethod.SetIsInputMethodEnabled(this, false); this.Loaded += this.NumbericTextBoxLoaded;
} #endregion Constructor #region Properties /// <summary>
/// 最大值,可取
/// </summary>
public double MaxValue
{
get { return (double)this.GetValue(MaxValueProperty); }
set { this.SetValue(MaxValueProperty, value); }
} /// <summary>
/// 最小值,可取
/// </summary>
public double MinValue
{
get { return (double)this.GetValue(MinValueProperty); }
set { this.SetValue(MinValueProperty, value); }
} /// <summary>
/// 精度,即精确到小数点后的位数
/// </summary>
public ushort Precision
{
get { return (ushort)this.GetValue(PrecisionProperty); }
set { this.SetValue(PrecisionProperty, value); }
} #endregion Properties protected virtual void OnPreviewTextChanged(TextChangedEventArgs e)
{
if (this.PreviewTextChanged != null)
{
this.PreviewTextChanged(this, e);
}
} #region Private Methods /// <summary>
/// 处理粘贴的情况
/// </summary>
protected virtual void HandlePaste()
{
this.isPaste = false; // 处理符号的标志
bool handledSybmol = false; // 处理小数点的标志
bool handledDot = false; // 当前位对应的基数
double baseNumber = 1; // 转换后的数字
double number = 0; // 上一次合法的数字
double lastNumber = 0; // 小数点后的位数
double precision = 0;
foreach (var c in this.Text)
{
if (!handledSybmol && (c == '-'))
{
baseNumber = -1;
handledSybmol = true;
} if ((c >= '0') && (c <= '9'))
{
int digit = c - '0';
if (!handledDot)
{
number = (number * baseNumber) + digit;
baseNumber = 10;
}
else
{
baseNumber = baseNumber / 10;
number += digit * baseNumber;
} // 正负号必须位于最前面
handledSybmol = true;
} if (c == '.')
{
// 精度已经够了
if (precision + 1 > this.Precision)
{
break;
} handledDot = true; // 此时正负号不能起作用
handledSybmol = true;
baseNumber = 0.1;
precision++;
} if ((number < this.MinValue) || (number > this.MaxValue))
{
this.Text = lastNumber.ToString(CultureInfo.InvariantCulture);
this.SelectionStart = this.Text.Length;
return;
} lastNumber = number;
} this.Text = number.ToString(CultureInfo.InvariantCulture);
this.SelectionStart = this.Text.Length;
} #endregion Private Methods #region Overrides of TextBoxBase #endregion #region Events Handling private void NumbericTextBoxLoaded(object sender, RoutedEventArgs e)
{
if (this.MinValue > this.MaxValue)
{
this.MinValue = this.MaxValue;
} if (string.IsNullOrEmpty(this.Text))
{
double val = (this.MaxValue + this.MinValue) / 2;
val = Math.Round(val, this.Precision); this.Text = val.ToString(CultureInfo.InvariantCulture);
} this.isPaste = true;
} /// <summary>
/// The numberic text box preview text input.
/// </summary>
/// <param name="sender"> The sender.</param>
/// <param name="e"> The e.</param>
private void NumbericTextBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
{
// 如果是粘贴不会引发该事件
this.isPaste = false; short val; // 输入非数字
if (!short.TryParse(e.Text, out val))
{
// 小于0时,可输入负号
if ((this.MinValue < 0) && (e.Text == "-"))
{
int minusPos = this.Text.IndexOf('-'); // 未输入负号且负号在第一位
if ((minusPos == -1) && (0 == this.SelectionStart))
{
return;
}
} // 精度大于0时,可输入小数点
if ((this.Precision > 0) && (e.Text == "."))
{
// 解决UpdateSourceTrigger为PropertyChanged时输入小数点文本与界面不一致的问题
if (this.SelectionStart > this.Text.Length)
{
e.Handled = true;
return;
} // 小数点位置
int dotPos = this.Text.IndexOf('.'); // 未存在小数点可输入
if (dotPos == -1)
{
return;
} // 已存在小数点但处于选中状态,也可输入小数点
if ((this.SelectionStart >= dotPos) && (this.SelectionLength > 0))
{
return;
}
} e.Handled = true;
}
else
{
int dotPos = this.Text.IndexOf('.');
int cursorIndex = this.SelectionStart; // 已经存在小数点,且小数点在光标后
if ((dotPos != -1) && (dotPos < cursorIndex))
{
// 不允许输入超过精度的数
if (((this.Text.Length - dotPos) > this.Precision) && (this.SelectionLength == 0))
{
e.Handled = true;
}
}
}
} /// <summary>
/// The numberic text box text changed.
/// </summary>
/// <param name="sender"> The sender.</param>
/// <param name="e"> The e.</param>
private void NumbericTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (this.lastLegalText == this.Text)
{
return;
} this.OnPreviewTextChanged(e); // 允许为空
if (string.IsNullOrEmpty(this.Text))
{
return;
} // 粘贴而来的文本
if (this.isPaste)
{
this.HandlePaste();
this.lastLegalText = this.Text; return;
} double val;
if (double.TryParse(this.Text, out val))
{
// 保存光标位置
int selectIndex = this.SelectionStart;
if ((val > this.MaxValue) || (val < this.MinValue))
{
this.Text = this.lastLegalText;
this.SelectionStart = selectIndex;
return;
} this.lastLegalText = this.Text;
} this.isPaste = true;
} /// <summary>
/// The numberic text box_ preview key down.
/// </summary>
/// <param name="sender"> The sender.</param>
/// <param name="e"> The e.</param>
private void NumbericTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
// 过滤空格
if (e.Key == Key.Space)
{
e.Handled = true;
}
} /// <summary>
/// The numberic text box_ lost focus.
/// </summary>
/// <param name="sender"> The sender.</param>
/// <param name="e"> The e.</param>
private void NumbericTextBoxLostFocus(object sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(this.Text))
{
this.Text = this.lastLegalText;
}
} #endregion Events Handling
}
}

IP地址输入框

IP地址输入框使用了上面的数字输入框,按点时可跳转到IP地址下一部分输入,支持IP地址的粘贴。代码较简单,且有注释,不再多说。

XAML

<UserControl x:Class="YiYan127.WPF.Controls.IpAddressControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:YiYan127.WPF.Controls">
<UniformGrid Columns="4" TextBoxBase.GotFocus="TextBox_OnGotFocus">
<DockPanel Margin="5,2">
<TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="." />
<controls:NumbericTextBox x:Name="IPPart1" MaxValue="255" MinValue="0"
Precision="0" />
</DockPanel>
<DockPanel Margin="0,2,5,2">
<TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="." />
<controls:NumbericTextBox x:Name="IPPart2" MaxValue="255" MinValue="0"
Precision="0" />
</DockPanel>
<DockPanel Margin="0,2,5,2">
<TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="." />
<controls:NumbericTextBox x:Name="IPPart3" MaxValue="255" MinValue="0"
Precision="0" />
</DockPanel>
<controls:NumbericTextBox x:Name="IPPart4" Margin="0,2,5,2" MaxValue="255"
MinValue="0" Precision="0" />
</UniformGrid>
</UserControl>

后台代码

namespace YiYan127.WPF.Controls
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; /// <summary>
/// IP地址输入框
/// </summary>
public partial class IpAddressControl
{
#region Fields /// <summary>
/// IP地址的依赖属性
/// </summary>
public static readonly DependencyProperty IPProperty = DependencyProperty.Register(
"IP",
typeof(string),
typeof(IpAddressControl),
new FrameworkPropertyMetadata(DefaultIP, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IPChangedCallback)); /// <summary>
/// IP地址的正则表达式
/// </summary>
public static readonly Regex IpRegex = new
Regex(@"^((2[0-4]\d|25[0-5]|(1\d{2})|([1-9]?[0-9]))\.){3}(2[0-4]\d|25[0-4]|(1\d{2})|([1-9][0-9])|([1-9]))$"); /// <summary>
/// 默认IP地址
/// </summary>
private const string DefaultIP = "127.0.0.1"; private static readonly Regex PartIprRegex = new Regex(@"^(\.?(2[0-4]\d|25[0-5]|(1\d{2})|([1-9]?[0-9]))\.?)+$"); /// <summary>
/// 输入框的集合
/// </summary>
private readonly List<NumbericTextBox> numbericTextBoxs = new List<NumbericTextBox>(); /// <summary>
/// 当前活动的输入框
/// </summary>
private NumbericTextBox currentNumbericTextBox; #endregion Fields #region Constructors public IpAddressControl()
{
InitializeComponent();
this.numbericTextBoxs.Add(this.IPPart1);
this.numbericTextBoxs.Add(this.IPPart2);
this.numbericTextBoxs.Add(this.IPPart3);
this.numbericTextBoxs.Add(this.IPPart4);
this.KeyUp += this.IpAddressControlKeyUp; this.UpdateParts(this); foreach (var numbericTextBox in this.numbericTextBoxs)
{
numbericTextBox.PreviewTextChanged += this.NumbericTextBox_OnPreviewTextChanged;
} foreach (var numbericTextBox in this.numbericTextBoxs)
{
numbericTextBox.TextChanged += this.TextBoxBase_OnTextChanged;
}
} #endregion Constructors #region Properties public string IP
{
get
{
return (string)GetValue(IPProperty);
} set
{
SetValue(IPProperty, value);
}
} #endregion Properties #region Private Methods /// <summary>
/// IP值改变的响应
/// </summary>
/// <param name="dependencyObject"></param>
/// <param name="dependencyPropertyChangedEventArgs"></param>
private static void IPChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (dependencyPropertyChangedEventArgs.NewValue == null)
{
throw new Exception("IP can not be null");
} var control = dependencyObject as IpAddressControl;
if (control != null)
{
control.UpdateParts(control);
}
} private void UpdateParts(IpAddressControl control)
{
string[] parts = control.IP.Split(new[] { '.' });
control.IPPart1.Text = parts[0];
control.IPPart2.Text = parts[1];
control.IPPart3.Text = parts[2];
control.IPPart4.Text = parts[3];
} #endregion Private Methods #region Event Handling /// <summary>
/// 按键松开的响应
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void IpAddressControlKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.OemPeriod || e.Key == Key.Decimal)
{
if (this.currentNumbericTextBox != null)
{
int index = this.numbericTextBoxs.IndexOf(this.currentNumbericTextBox);
int next = (index + 1) % this.numbericTextBoxs.Count;
this.numbericTextBoxs[next].Focus();
this.numbericTextBoxs[next].SelectionStart = this.numbericTextBoxs[next].Text.Length;
}
}
} /// <summary>
/// 获得焦点的响应
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TextBox_OnGotFocus(object sender, RoutedEventArgs e)
{
this.currentNumbericTextBox = e.OriginalSource as NumbericTextBox;
} private void NumbericTextBox_OnPreviewTextChanged(object sender, TextChangedEventArgs e)
{
var numbericTextBox = sender as NumbericTextBox;
Contract.Assert(numbericTextBox != null); if (PartIprRegex.IsMatch(numbericTextBox.Text))
{
var ips = numbericTextBox.Text.Split('.'); if (ips.Length == 1)
{
return;
} int index = this.numbericTextBoxs.IndexOf(numbericTextBox);
int pointer2Ips = 0;
for (int i = index; i < this.numbericTextBoxs.Count; i++)
{
while (pointer2Ips < ips.Length && string.IsNullOrEmpty(ips[pointer2Ips]))
{
pointer2Ips++;
} if (pointer2Ips >= ips.Length)
{
return;
} this.numbericTextBoxs[i].Text = ips[pointer2Ips];
pointer2Ips++;
}
}
} private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
var ip = string.Format(
"{0}.{1}.{2}.{3}",
this.IPPart1.Text,
this.IPPart2.Text,
this.IPPart3.Text,
this.IPPart4.Text);
if (IpRegex.IsMatch(ip))
{
this.IP = ip;
}
} #endregion Event Handling
}
}

WPF数字输入框和IP地址输入框的更多相关文章

  1. [Qt] IP地址输入框实现

    封装了一个ip地址的输入框.网络上下载了份代码,找不到哪里的了.经过修改之后,尽力让它的行为和windows的IP地址输入框的行为看起来像些.代码如下: //ipaddredit.h #ifndef ...

  2. IP地址输入框

    <style> div.IPDiv{background:#ffffff;width:120;font-size:9pt;text-align:center;border:2 ridge ...

  3. JavaScript实现IP地址的输入框方式

    最近遇到一些这样的需求:实现一种IP地址的输入方式,就是输入3个字符或自动跳到下一个输入框内,删除的时候,一个输入框没有了字符,自动跳回上一个输入框.看到这里,相信大家都有一些想法了,没错,这种方法就 ...

  4. Qt编写自定义控件68-IP地址输入框

    一.前言 这个IP地址输入框控件,估计写烂了,网上随便一搜索,保证一大堆,估计也是因为这个控件太容易了,非常适合新手练手,一般的思路都是用4个qlineedit控件拼起来,然后每个输入框设置正则表达式 ...

  5. WPF IP地址输入控件的实现

    一.前言 WPF没有内置IP地址输入控件,因此我们需要通过自己定义实现. 我们先看一下IP地址输入控件有什么特性: 输满三个数字焦点会往右移 键盘←→可以空光标移动 任意位置可复制整段IP地址,且支持 ...

  6. (C#)IP地址与数字地址相互转换

    站长网IP查询地址:http://tool.chinaz.com/ip/ 和ip地址转换为数字的工具地址:http://www.msxindl.com/tools/ip/ip_num.asp 可以看到 ...

  7. php实现IP地址和数字相互转换

    echo $a=ip2long ("202.97.224.68");//地址转换成数字 系统自带的函数 注:这里面有一个要注意的地方,大概由于PHP无法正确判断转换完的数字类型,出 ...

  8. IP地址与数字地址相互转换

    /// <summary> /// IP地址转换成数字 /// </summary> /// <param name="addr">IP地址&l ...

  9. 【Web探索之旅】第三部分第二课:IP地址和域名

    内容简介 1.第三部分第二课:IP地址和域名 2.第三部分第三课预告:协议 第三部分第二课:IP地址和域名 上一课我们说了在Web之中,全球各地有无数台机器,有些充当客户机,有些作为服务器. 那么这些 ...

随机推荐

  1. 「C语言」文件的概念与简单数据流的读写函数

    写完「C语言」单链表/双向链表的建立/遍历/插入/删除 后,如何将内存中的链表信息及时的保存到文件中,又能够及时的从文件中读取出来进行处理,便需要用到”文件“的相关知识点进行文件的输入.输出. 其实, ...

  2. playframework文档未提及,但你能做的事

    这里记录一些play!框架的文档未提及,但是可以做的一些事playframe版本1.1 1.application.conf文件可以拆分可以把application.conf文件拆成多个,需要在app ...

  3. virtualenv and virtualenvwrapper on Ubuntu 14.04

    In this post I’ll go over my attempt to setup virtual environments for Python development. Most Pyth ...

  4. Exchange 2013 申请证书

    最近在了解Exchange2013,Exchange2013相对于Lync安装相对容易一点,安装完成并不代表就可以用了,还要一些基本的配制,首先介绍一下如何从证书服务器申请 CA. 一.DNS 创建解 ...

  5. .NET 面试基本技术整理

    这篇文章主要 整理出来的大部分公司需要的技术 以及一些学习链接,进行恶补一下,以免面试官考倒你 其中也整理了一些面试题需要的可以点击链接 需要掌握的技术 基础概念需要 面向对象 OOD/OOP OOD ...

  6. [转] X-RIME: 基于Hadoop的开源大规模社交网络分析工具

    转自http://www.dataguru.cn/forum.php?mod=viewthread&tid=286174 随着互联网的快速发展,涌现出了一大批以Facebook,Twitter ...

  7. 【转】提高C#编程水平的50个要点

    1.总是用属性 (Property) 来代替可访问的数据成员2.在 readonly 和 const 之间,优先使用 readonly3.在 as 和 强制类型转换之间,优先使用 as 操作符4.使用 ...

  8. 在Mac上配置Android adb命令

    一 adb定义: adb(android debug bridge)是android系统中的一种命令行工具,通过它可以和android设备或模拟器通信. 二 在Mac上的配置过程 启动终端 进入当前用 ...

  9. 安卓第十三天笔记-服务(Service)

    安卓第十三天笔记-服务(Service) Servcie服务 1.服务概念 服务 windows 服务没有界面,一直运行在后台, 运行在独立的一个进程里面 android 服务没有界面,一直运行在后台 ...

  10. IOS开发之网络编程--文件压缩和解压缩

    前言: QQ表情包就用到了解压缩,从网络下载的那么多表情文件格式并不是一个一个图片文件,而是多个图片压缩而成的表情压缩包.下面介绍的是iOS开发中会用到的压缩和解压缩的第三方框架的使用. 注意: 这个 ...