通用 Windows 应用 (UWP) 的数字文本框






3.77/5 (4投票s)
仅限数字的文本框,用于通用 Windows 应用,可以选择允许负数并设置自定义数字模式。
引言
增强 TextBox 的类,可选择仅允许数字、限制为正数,并使用“.”(点)作为十进制字符以跳转到其小数部分(如果有)。
背景
仅限数字的 TextBox 可以使用各种技术实现,最常见的是通过正确处理 Key Events,但是 Microsoft 建议不要在 UWP 中使用 Key Events 处理文本,因为它有各种输入法(键盘、触摸板、笔、游戏控制器、电视遥控器等),并且没有适当的方法知道特定键或键组合将生成哪个“文本”。
Microsoft 强烈建议通过验证过程(显示气球和警告错误)来完成,并让用户进行更正。
我主要编写金融应用程序,这些应用程序使用大量货币字段,它们特别需要同时处理数字数据,并根据用户文化显示正确格式化的数字,以便用户可以轻松地进行配对。
对我来说,输入数字(例如:41234456.4556)然后对其进行格式化,编写起来更加困难,因为它让我不断检查我正在输入的内容是否正确;但是如果我可以在编写时看到格式化的数字(例如:41,234,456.4556),那会使很多工作变得更容易。
还需要避免应用程序的某些字段中出现负数,虽然避免此类错误的最佳方法是通过验证,但可以完全通过避免此类错误的方式来处理。
使用代码
要使用该类,请使用 Tag 属性中的默认参数创建 ClsNumTextTagIn 的新实例,并创建 TextBox 控件的 3 个必要事件
- SelectionChanged:在文本选择或指针位置更改时刷新类中的原始数据;
- TextChanged:在 TextChanging 事件中处理后刷新类中的原始数据;
- TextChanging: 处理文本。
<TextBox InputScope="Number" SelectionChanged="TextBox_SelectionChanged" TextChanged="TextBox_TextChanged" TextChanging="TextBox_TextChanging">
<TextBox.Tag>
<local:ClsNumTextTagIn />
</TextBox.Tag>
</TextBox>
private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
TextBox tb = (TextBox)sender;
if (tb.Tag != null)
{
((ClsNumTextTagIn)tb.Tag).Refresh(tb);
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox tb = (TextBox)sender;
if (tb.Tag != null)
{
((ClsNumTextTagIn)tb.Tag).Refresh(tb);
}
}
private void TextBox_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
TextBox tb = (TextBox)sender;
if (tb.Tag != null)
{
((ClsNumTextTagIn)tb.Tag).NumericText(tb);
}
}
提示:
仅整数:MyPattern = "N0"。<x:String>/</x:String>
</local:ClsNumTextTagIn.LstRemStr>
<TextBox InputScope="Number" SelectionChanged="TextBox_SelectionChanged" TextChanged="TextBox_TextChanged" TextChanging="TextBox_TextChanging">
<TextBox.Tag>
<local:ClsNumTextTagIn MyPattern="N4" IsNumOnly="False" IsDotSepa="True" CanNegNum="False">
<local:ClsNumTextTagIn.LstRemStr>
<x:String>/</x:String>
<x:String>,</x:String>
</local:ClsNumTextTagIn.LstRemStr>
</local:ClsNumTextTagIn>
</TextBox.Tag>
</TextBox>
ClsNumTextTagIn
类public class ClsNumTextTagIn
{
private class cTxtBoxProperty
{
public int iBeg { get; set; }
public int iSel { get; set; }
public int iLen { get; set; }
public string sLef { get; set; }
public string sRig { get; set; }
public string sSel { get; set; }
public string sTxt { get; set; }
public cTxtBoxProperty(Windows.UI.Xaml.Controls.TextBox sender)
{
iBeg = sender.SelectionStart;
iSel = sender.SelectionLength;
iLen = sender.Text.Length;
sLef = sender.Text.Substring(0, sender.SelectionStart);
sRig = sender.Text.Substring(sender.SelectionStart + sender.SelectionLength);
sSel = sender.SelectedText;
sTxt = sender.Text;
}
}
private cTxtBoxProperty org { get; set; }
public string MyPattern { get; set; }
public bool IsNumOnly { get; set; }
public bool IsDotSepa { get; set; }
public bool CanNegNum { get; set; }
public System.Collections.Generic.List<string> LstRemStr { get; set; }
public object Tag { get; set; } //use this Tag
public ClsNumTextTagIn()
{
//org = new cTxtBoxProperty(sender);
MyPattern = "N2";
IsNumOnly = true;
IsDotSepa = true;
CanNegNum = true;
LstRemStr = new System.Collections.Generic.List<string>();
//Tag = anyTag; //use this Tag
}
/// <summary>
/// Routine to test numeric if current TextChanging is numeric and display accordinly to current culture.
///
///<param name="sender" />Target TextBox
///<param name="pattern" />Currently only Numeric Pattern is supported eg: "N2", "N6"...etc. N stands for number.
///<param name="numbersOnly" />true = allow numbers only; false = leave text if its not numeric decimal.
///<param name="dotAsDecimalSeparator" />true = "." counts as decimal separator (regardless of culture) and it will go for decimal part of pattern (if any).
///<param name="allowNegativeNumbers" />true = can have negative numbers, false = prevents negative numbers.
///<param name="lstRemoveStrings" />List of any other strings you wish remove eg: "(", " / ", ")", " - ", " + "...etc, if none just pass "null" parameter.
///<param name="anyTag" />Use paramenter to pass some object in Tag, if none just pass "null" parameter.
public ClsNumTextTagIn(Windows.UI.Xaml.Controls.TextBox sender, string pattern, bool numbersOnly, bool dotAsDecimalSeparator, bool allowNegativeNumbers, System.Collections.Generic.List<string> lstRemoveStrings, System.Object anyTag)
{
org = new cTxtBoxProperty(sender);
MyPattern = pattern;
IsNumOnly = numbersOnly;
IsDotSepa = dotAsDecimalSeparator;
CanNegNum = allowNegativeNumbers;
LstRemStr = lstRemoveStrings;
Tag = anyTag; //use this Tag
}
public void Refresh(Windows.UI.Xaml.Controls.TextBox sender)
{
org = new cTxtBoxProperty(sender);
}
public void NumericText(Windows.UI.Xaml.Controls.TextBox sender)
{ //logics
if (org == null)
{
org = new cTxtBoxProperty(sender);
}
System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentUICulture;
cTxtBoxProperty edi = new cTxtBoxProperty(sender);
string sPtn = MyPattern;
string sAdd = string.Empty;
string sSub = string.Empty;
int iChg = edi.iLen - org.iBeg - org.sRig.Length;
if (iChg >= 0)
{
sAdd = edi.sTxt.Substring(org.iBeg, iChg);
sSub = org.sSel;
}
else
{ //backspace?
if (org.iLen >= edi.iLen)
{
if (org.iBeg - (org.iLen - edi.iLen) >= 0)
{
sSub = org.sTxt.Substring(org.iBeg - (org.iLen - edi.iLen), (org.iLen - edi.iLen));
}
}
}
int iBgx = edi.iBeg;
int iSlx = 0;
string sFnl = org.sTxt;
int iNeg = sFnl.IndexOf(ci.NumberFormat.NegativeSign);
int iDot = sFnl.IndexOf(ci.NumberFormat.NumberDecimalSeparator);
if (sAdd == ci.NumberFormat.NegativeSign)
{
if (iNeg == -1)
{ //add
if (CanNegNum)
{//accepts negative numbers
//if (ci.TextInfo.IsRightToLeft)
//{
// sFnl = ci.NumberFormat.NegativeSign + sFnl; //sFnl = sFnl + ci.NumberFormat.NegativeSign;
// iBgx = edi.iBeg - ci.NumberFormat.NegativeSign.Length;
//}
//else
//{
sFnl = ci.NumberFormat.NegativeSign + sFnl;
iBgx = edi.iBeg;
//}
}
else
{//does not accepts negative numbers
//if (ci.TextInfo.IsRightToLeft)
//{
iBgx = edi.iBeg - ci.NumberFormat.NegativeSign.Length;
//}
//else
//{
// iBgx = edi.iBeg;
//}
}
}
else
{//remove
sFnl = sFnl.Remove(iNeg, ci.NumberFormat.NegativeSign.Length);
if (iNeg >= iBgx)
{
iBgx = edi.iBeg - ci.NumberFormat.NegativeSign.Length;
}
else
{
//if (ci.TextInfo.IsRightToLeft)
//{
// iBgx = edi.iBeg - ci.NumberFormat.NegativeSign.Length;
//}
//else
//{
iBgx = edi.iBeg - ci.NumberFormat.NegativeSign.Length - ci.NumberFormat.NegativeSign.Length;
//}
}
}
} //end NegativeSign
else if (sAdd == ci.NumberFormat.NumberDecimalSeparator)
{ //NumberDecimalSeparator
if (iDot == -1)
{ //add
sFnl = edi.sTxt;
}
else
{ //go to point
sFnl = org.sTxt;
iBgx = iDot + ci.NumberFormat.NumberDecimalSeparator.Length;
}
}
else if (sAdd == ".")
{ //dotAsDecimalSeparator
if (IsDotSepa)
{
if (iDot == -1)
{ //add
sFnl = edi.sTxt;
}
else
{ //go to point
sFnl = org.sTxt;
iBgx = iDot + ci.NumberFormat.NumberDecimalSeparator.Length;
}
}
else
{
sFnl = edi.sTxt; //override
}
}
else if (sSub == ci.NumberFormat.NumberGroupSeparator & org.iSel == 0)
{ //backspace and GroupSeparator
if (edi.iBeg == 0)
{
sFnl = edi.sTxt;
}
else
{
sFnl = edi.sLef.Replace(ci.NumberFormat.NumberGroupSeparator, string.Empty);
int iRem = edi.sLef.Length - sFnl.Length;
sFnl = sFnl.Substring(0, edi.sLef.Length - iRem - 1) + edi.sRig;
iBgx = iBgx - iRem - 1;
}
}//end backspace and GroupSeparator
else
{
sFnl = edi.sTxt;
}
if (iBgx > sFnl.Length)
{
iBgx = sFnl.Length;
}
string sLfx = sFnl.Substring(0, iBgx);
string sRgx = sFnl.Substring(iBgx);
sRgx = sRgx.Replace(ci.NumberFormat.NumberGroupSeparator, string.Empty);
sFnl = sFnl.Replace(ci.NumberFormat.NumberGroupSeparator, string.Empty);
if (LstRemStr != null)
{
foreach (string sRem in LstRemStr)
{
sRgx = sRgx.Replace(sRem, string.Empty); //automatically trimmed
sFnl = sFnl.Replace(sRem, string.Empty); //automatically trimmed
}
}
//s += "Rgx: " + sRgx.ToString() + Environment.NewLine;
//s += "Fnl: " + sFnl.ToString() + Environment.NewLine;
if (!CanNegNum)
{//remove negative sign
sRgx = sRgx.Replace(ci.NumberFormat.NegativeSign, string.Empty);
sFnl = sFnl.Replace(ci.NumberFormat.NegativeSign, string.Empty);
}
decimal dFnl = decimal.Zero;
decimal dRgx = decimal.Zero;
if (decimal.TryParse(sFnl, out dFnl))
{
int iDtx = sFnl.IndexOf(ci.NumberFormat.NumberDecimalSeparator);
int iDif = sFnl.Length - dFnl.ToString().Length;
if (iDtx == -1)
{//DecimalSeparator does not exist
sFnl = dFnl.ToString(sPtn);
int iTmp = sFnl.IndexOf(ci.NumberFormat.NumberDecimalSeparator);
if (iTmp == -1)
{//DecimalSeparator not found after apply pattern
if (sRgx == string.Empty)
{
iBgx = sFnl.Length;
}
else
{
if (sRgx.StartsWith(decimal.Zero.ToString()))
{
sRgx = decimal.One.ToString() + sRgx;
if (decimal.TryParse(sRgx, out dRgx))
{
sRgx = dRgx.ToString(sPtn);
if (sRgx.StartsWith(decimal.One.ToString() + ci.NumberFormat.NumberDecimalSeparator))
{
iBgx = sFnl.Length - sRgx.Length + (decimal.One.ToString() + ci.NumberFormat.NumberDecimalSeparator).Length;
}
else
{
iBgx = sFnl.Length - sRgx.Length + decimal.One.ToString().Length;
}
}
}
else
{
if (decimal.TryParse(sRgx, out dRgx))
{
sRgx = dRgx.ToString(sPtn);
iBgx = sFnl.Length - sRgx.Length;
}
}
}
}
else
{//DecimalSeparator found after apply pattern
if (sRgx == string.Empty)
{
iBgx = iTmp;
}
else
{
if (sRgx.StartsWith(decimal.Zero.ToString()))
{
sRgx = decimal.One.ToString() + sRgx;
if (decimal.TryParse(sRgx, out dRgx))
{
sRgx = dRgx.ToString(sPtn);
if (sRgx.StartsWith(decimal.One.ToString() + ci.NumberFormat.NumberDecimalSeparator))
{
iBgx = sFnl.Length - sRgx.Length + (decimal.One.ToString() + ci.NumberFormat.NumberDecimalSeparator).Length;
}
else
{
iBgx = sFnl.Length - sRgx.Length + decimal.One.ToString().Length;
}
}
}
else
{
if (decimal.TryParse(sRgx, out dRgx))
{
sRgx = dRgx.ToString(sPtn);
iBgx = sFnl.Length - sRgx.Length;
}
}
}
}
if (iBgx < 0)
{
iBgx = 0;
}
}
else
{//DecimalSeparator does exist
if (sFnl.Length - sRgx.Length <= iDtx)
{//before DecimalSeparator
sFnl = dFnl.ToString(sPtn);
if (sRgx.StartsWith(decimal.Zero.ToString()))
{
sRgx = decimal.One.ToString() + sRgx;
if (decimal.TryParse(sRgx, out dRgx))
{
sRgx = dRgx.ToString(sPtn);
if (sRgx.StartsWith(decimal.One.ToString() + ci.NumberFormat.NumberDecimalSeparator))
{
iBgx = sFnl.Length - sRgx.Length + (decimal.One.ToString() + ci.NumberFormat.NumberDecimalSeparator).Length;
}
else
{
iBgx = sFnl.Length - sRgx.Length + decimal.One.ToString().Length;
}
}
}
else if (sRgx.StartsWith(ci.NumberFormat.NumberDecimalSeparator))
{
if (decimal.TryParse(sRgx, out dRgx))
{
sRgx = dRgx.ToString(sPtn);
iBgx = sFnl.Length - sRgx.Length + ci.NumberFormat.NumberDecimalSeparator.Length;
}
}
else
{
if (decimal.TryParse(sRgx, out dRgx))
{
sRgx = dRgx.ToString(sPtn);
iBgx = sFnl.Length - sRgx.Length;
}
}
if (iBgx < 0)
{
iBgx = 0;
}
}
else
{//after DecimalSeparator
sFnl = dFnl.ToString(sPtn);
int iTmp = sFnl.IndexOf(ci.NumberFormat.NumberDecimalSeparator); //override
if (iTmp == -1)
{// pattern does not accept DecimalSeparator
iBgx = sFnl.Length;
}
else
{
if (sSub != string.Empty & sAdd == string.Empty)
{// if backspace (after decimal separator)
if (iTmp + 1 < iBgx)
{
iBgx = iBgx - 1;
}
else
{
iBgx = iTmp;
}
}
if (sFnl.Length > iBgx)
{
if (iBgx > iTmp)
{
iSlx = 1;
}
}
}
}
}
sender.Text = sFnl;
sender.SelectionStart = iBgx;
sender.SelectionLength = iSlx;
}//end of if decimal
else
{//not decimal
if (IsNumOnly)
{ //IsNumOnly =true
decimal dOrg = decimal.Zero;
if (sFnl != string.Empty && decimal.TryParse(org.sTxt, out dOrg))
{
sender.Text = org.sTxt;
sender.SelectionStart = org.iBeg;
sender.SelectionLength = org.iSel;
}
else
{
decimal dAdd = decimal.Zero;
decimal.TryParse(sAdd, out dAdd);
sFnl = dAdd.ToString(sPtn);
int iTmp = sFnl.IndexOf(ci.NumberFormat.NumberDecimalSeparator);
if (iTmp == -1)
{
iBgx = sFnl.Length;
}
else
{
iBgx = iTmp;
}
sender.Text = sFnl;
sender.SelectionStart = iBgx;
sender.SelectionLength = 0;
}
}
else
{ //numbersOnly false
sender.Text = edi.sTxt;
sender.SelectionStart = edi.iBeg;
sender.SelectionLength = edi.iSel;
}
}
}
}
关注点
可以改进 NumericText
例程以包括允许的最小和最大数字的参数。
小数点的当前文本处理方式可能会让一些用户感到烦恼。
尚未在具有 从右到左
模式的语言中进行测试!
请帮助识别任何错误或改进建议。
注意:如果您正在不同的平台上测试附加的示例应用程序,请相应地将调试更改为 x86 或 x64。
谢谢。
历史
版本 1.01 - 添加了示例应用程序和屏幕截图; 包含图像以显示不同的世界文化。
版本 1.00 - 原始。