在WPF中的数字数据输入

你如何处理WPF应用程序中的数值输入?

如果没有NumericUpDown控件,我一直在使用TextBox并使用下面的代码处理它的PreviewKeyDown事件,但它非常难看。

有没有人找到一种更优雅的方式来从用户那里获取数字数据而不依赖于第三方控件?

private void NumericEditPreviewKeyDown(object sender, KeyEventArgs e)
{
    bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9) || e.Key == Key.Decimal;
    bool isNumeric = (e.Key >= Key.D0 && e.Key <= Key.D9) || e.Key == Key.OemPeriod;

    if ((isNumeric || isNumPadNumeric) && Keyboard.Modifiers != ModifierKeys.None)
    {
        e.Handled = true;
        return;
    }

    bool isControl = ((Keyboard.Modifiers != ModifierKeys.None && Keyboard.Modifiers != ModifierKeys.Shift)
        || e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Insert
        || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up
        || e.Key == Key.Tab
        || e.Key == Key.PageDown || e.Key == Key.PageUp
        || e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Escape
        || e.Key == Key.Home || e.Key == Key.End);

    e.Handled = !isControl && !isNumeric && !isNumPadNumeric;
}
0

16 答案

叫我疯了,但为什么不把加号和减号按钮放在TextBox控件的两边,并简单地阻止TextBox接收光标焦点,从而创建自己便宜的NumericUpDown控件?

0
额外
我不敢相信我在2年之后捍卫了这个回应,但是入场速度与它无关 - 原始的海报需要一个NumericUpDown文本框,并且这对于该场景是有效的。他对速度一无所知。如果你想要8080,你只需点击Tab键并在键盘上输入8080;没有什么大不了的。我担心任何真正认为我在说几千次单击加号按钮的人的大脑是一个很好的解决方案。
额外 作者 tags2k,
数据录入的速度。我们有数据输入操作员,他们使用键盘敲击数据(通常只是数字键盘),因此他们无法通过输入屏幕到达鼠标的一半位置。
额外 作者 Matt Hamilton,
@Greg,我同意tags2k在这里的评论,NUD控件在适当使用时有效,比如默认qty可能为1的订单输入,但用户可以选择更多订单。在决定使用NUD时会出现两个问题,1)用户是否最有可能将手放在鼠标上,2)在大多数情况下,可能没有变化或只有少数增量/递减。
额外 作者 Brett Ryan,
在这种情况下,编写您自己的可重复使用的NUD控件并在整个应用程序中使用它!
额外 作者 RQDQ,
需要多长时间才能输入4位数的引脚?或者在账户之间转移大量资金?或者添加两个大数字?或者将端口号设置为8080?坏。
额外 作者 Greg Sansom,
你疯了。 :)
额外 作者 F.D.Castel,

如果用户在使用数据之前提交数据,也可以尝试使用数据验证。这样做我发现相当简单和清洁比用钥匙摆弄。

否则,您总是可以禁用粘贴!

0
额外
无论如何,我肯定会验证。我只是为了防止用户尽可能地犯错误,以至于他们几乎不可能看到错误弹出窗口。
额外 作者 Matt Hamilton,

怎么样:

protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
    e.Handled = !AreAllValidNumericChars(e.Text);
    base.OnPreviewTextInput(e);
}

private bool AreAllValidNumericChars(string str)
{
    foreach(char c in str)
    {
        if(!Char.IsNumber(c)) return false;
    }

    return true;
}
0
额外
我刚刚意识到,这两种方法都不会阻止用户将非数字字符粘贴到控件中,但这不是一个大问题。我想将你的回答标记为答案,因为我们可以得到尽可能接近的答案。谢谢!
额外 作者 Matt Hamilton,
这比我的方法更好,它的代码更少,并且仍允许像箭头键和退格键之类的控制键。
额外 作者 Matt Hamilton,
为什么不Double.TryParse?
额外 作者 Louis Rhys,
这将在小数点上失败。如果用户输入5.3。它也会在负数上失败,因为“ - ”不是数字。容易修复,但只是想我会在那里提出这种谨慎。
额外 作者 Kelly,
不如使用绑定验证。
额外 作者 Eric,
e.Handled =!e.Text.ToCharArray()。All(c => Char.IsNumber(c));
额外 作者 si618,
我推荐juanagui的答案,因为它处理非数字数据粘贴 stackoverflow.com/a/2673131/755404
额外 作者 Darren,
e.Handled =!e.Text.All(Char.IsNumber);
额外 作者 Olson.dev,

这就是我的做法。它使用正则表达式来检查框中的文本是否为数字。

Regex NumEx = new Regex(@"^-?\d*\.?\d*$");

private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    if (sender is TextBox)
    {
        string text = (sender as TextBox).Text + e.Text;
        e.Handled = !NumEx.IsMatch(text);
    }
    else
        throw new NotImplementedException("TextBox_PreviewTextInput Can only Handle TextBoxes");
}

现在有一个更好的方法在WPF和Silverlight做到这一点。如果你的控件绑定到一个属性,所有你需要做的就是改变你的约束力的声明一点。使用您绑定了以下内容:


请注意,您也可以在自定义属性上使用此属性,如果框中的值无效并且控件将以红色边框突出显示,则您只需抛出异常。如果您单击红色边框的右上角,则会弹出异常消息。

0
额外
用于调出绑定方法的+1。
额外 作者 Justin R.,
我喜欢Regex解决方案。但是这里的问题是我不能为现有数字添加减号,因为它假设新文本总是附加到旧文本。任何建议?
额外 作者 newman,
正则表达式解决方案几乎不如BindingValidation方法。
额外 作者 Eric,

结合几个这些答案的想法,我创建了一个NumericTextBox

  • 处理小数
  • 进行一些基本验证以确保输入的是“ - ”或“。”是有效的
  • 处理粘贴的值

如果您能想到应该包含的任何其他逻辑,请随时更新。

public class NumericTextBox : TextBox
{
    public NumericTextBox()
    {
        DataObject.AddPastingHandler(this, OnPaste);
    }

    private void OnPaste(object sender, DataObjectPastingEventArgs dataObjectPastingEventArgs)
    {
        var isText = dataObjectPastingEventArgs.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true);

        if (isText)
        {
            var text = dataObjectPastingEventArgs.SourceDataObject.GetData(DataFormats.Text) as string;
            if (IsTextValid(text))
            {
                return;
            }
        }

        dataObjectPastingEventArgs.CancelCommand();
    }

    private bool IsTextValid(string enteredText)
    {
        if (!enteredText.All(c => Char.IsNumber(c) || c == '.' || c == '-'))
        {
            return false;
        }

        //We only validation against unselected text since the selected text will be replaced by the entered text
        var unselectedText = this.Text.Remove(SelectionStart, SelectionLength);

        if (enteredText == "." && unselectedText.Contains("."))
        {
            return false;
        }

        if (enteredText == "-" && unselectedText.Length > 0)
        {
            return false;
        }

        return true;
    }

    protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
    {
        e.Handled = !IsTextValid(e.Text);
        base.OnPreviewTextInput(e);
    }
}
0
额外

我一直在使用附加属性来允许用户使用向上和向下键来更改文本框中的值。要使用它,你只需使用

100

这实际上并没有解决在这个问题中引用的验证问题,但它解决了我所做的关于没有数字上/下控制的问题。稍微使用它,我想我实际上可能比旧的数字上/下控制更好。

代码并不完美,但它处理我需要它处理的情况:

  • Up arrow, Down arrow
  • Shift + Up arrow, Shift + Down arrow
  • Page Up, Page Down
  • Binding Converter on the text property

后面的代码

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

namespace Helpers
{
    public class TextBoxNumbers
    {    
        public static Decimal GetSingleDelta(DependencyObject obj)
        {
            return (Decimal)obj.GetValue(SingleDeltaProperty);
        }

        public static void SetSingleDelta(DependencyObject obj, Decimal value)
        {
            obj.SetValue(SingleDeltaProperty, value);
        }

        // Using a DependencyProperty as the backing store for SingleValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SingleDeltaProperty =
            DependencyProperty.RegisterAttached("SingleDelta", typeof(Decimal), typeof(TextBoxNumbers), new UIPropertyMetadata(0.0m, new PropertyChangedCallback(f)));

        public static void f(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            TextBox t = o as TextBox;

            if (t == null)
                return;

            t.PreviewKeyDown += new System.Windows.Input.KeyEventHandler(t_PreviewKeyDown);
        }

        private static Decimal GetSingleValue(DependencyObject obj)
        {
            return GetSingleDelta(obj);
        }

        private static Decimal GetDoubleValue(DependencyObject obj)
        {
            return GetSingleValue(obj) * 10;
        }

        private static Decimal GetTripleValue(DependencyObject obj)
        {
            return GetSingleValue(obj) * 100;
        }

        static void t_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            TextBox t = sender as TextBox;
            Decimal i;

            if (t == null)
                return;

            if (!Decimal.TryParse(t.Text, out i))
                return;

            switch (e.Key)
            {
                case System.Windows.Input.Key.Up:
                    if (Keyboard.Modifiers == ModifierKeys.Shift)
                        i += GetDoubleValue(t);
                    else
                        i += GetSingleValue(t);
                    break;

                case System.Windows.Input.Key.Down:
                    if (Keyboard.Modifiers == ModifierKeys.Shift)
                        i -= GetDoubleValue(t);
                    else
                        i -= GetSingleValue(t);
                    break;

                case System.Windows.Input.Key.PageUp:
                    i += GetTripleValue(t);
                    break;

                case System.Windows.Input.Key.PageDown:
                    i -= GetTripleValue(t);
                    break;

                default:
                    return;
            }

            if (BindingOperations.IsDataBound(t, TextBox.TextProperty))
            {
                try
                {
                    Binding binding = BindingOperations.GetBinding(t, TextBox.TextProperty);
                    t.Text = (string)binding.Converter.Convert(i, null, binding.ConverterParameter, binding.ConverterCulture);
                }
                catch
                {
                    t.Text = i.ToString();
                }
            }
            else
                t.Text = i.ToString();
        }
    }
}
0
额外
这是一段很棒的代码。我试过的各种NUD控制器一直都是越野车。你的作品很有魅力,谢谢。 :)
额外 作者 Echilon,
它适合MVVM。 +1
额外 作者 Ignacio Soler Garcia,

你不能只使用下面的东西吗?

int numericValue = 0;

if (false == int.TryParse(yourInput, out numericValue))
{
    // handle non-numeric input
}
0
额外
Private Sub Value1TextBox_PreviewTextInput(ByVal sender As Object, ByVal e As TextCompositionEventArgs) Handles Value1TextBox.PreviewTextInput
    Try
        If Not IsNumeric(e.Text) Then
            e.Handled = True
        End If
    Catch ex As Exception
    End Try
End Sub

为我工作。

0
额外
...只要用户不输入 - 或。
额外 作者 Greg Sansom,
void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
    string sVal = e.Text;
    int val = 0;

    if (sVal != null && sVal.Length > 0)
    {
        if (int.TryParse(sVal, out val))
        {
            e.Handled = false;
        }
        else
        {
            e.Handled = true;
        }
    }
}
0
额外
这不起作用,因为e.Text仅包含添加的文本,而不包含整个TextBox内容。价值可能只是“。”
额外 作者 Greg Sansom,
public class NumericTextBox : TextBox
{
    public NumericTextBox()
        : base()
    {
        DataObject.AddPastingHandler(this, new DataObjectPastingEventHandler(CheckPasteFormat));
    }

    private Boolean CheckFormat(string text)
    {
        short val;
        return Int16.TryParse(text, out val);
    }

    private void CheckPasteFormat(object sender, DataObjectPastingEventArgs e)
    {
        var isText = e.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true);

        if (isText)
        {
            var text = e.SourceDataObject.GetData(DataFormats.Text) as string;
            if (CheckFormat(text))
            {
                return;
            }
        }

        e.CancelCommand();
    }

    protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
    {
        if (!CheckFormat(e.Text))
        {
            e.Handled = true;
        }
        else
        {
            base.OnPreviewTextInput(e);
        }
    }
}

此外,您可以通过提供适当的依赖项属性来自定义解析行为。

0
额外
为我完美工作
额外 作者 Darren,
这不起作用,因为e.Text仅包含添加的文本,而不包含整个TextBox内容。该值可以是“。”
额外 作者 Greg Sansom,

将此添加到主解决方案以确保在清除文本框时将绑定更新为零。

protected override void OnPreviewKeyUp(System.Windows.Input.KeyEventArgs e)
{
    base.OnPreviewKeyUp(e);

    if (BindingOperations.IsDataBound(this, TextBox.TextProperty))
    {
        if (this.Text.Length == 0)
        {
            this.SetValue(TextBox.TextProperty, "0");
            this.SelectAll();
        }
    }
}
0
额外
有趣。它是否需要包装在IsDataBound调用中?
额外 作者 Matt Hamilton,

我决定简化标记为回答的答复,使用LINQ表达式基本上是2行。

e.Handled = !e.Text.All(Char.IsNumber);
base.OnPreviewTextInput(e);
0
额外
这很好,很整齐。但我该如何接受“。”和“ - ”?
额外 作者 newman,
e.Text.All(cc => Char.IsNumber(cc)|| cc =='。'|| cc ==' - ')
额外 作者 user7116,
不幸的是,当用户输入一个空格时,OnPreviewTextInput不会被调用。这可以通过重写OnPreviewKeyDown单独处理,无论如何用户仍然可以粘贴非数字字符。 PS:你可以通过编写完整的方法来改进你的答案: protected override void OnPreviewTextInput(TextCompositionEventArgs e){...}
额外 作者 Gobe,

我的 Arcturus 答案版本可以更改用于工作的转换方法与int / uint /十进制/字节(对于颜色)或任何其他数字格式你喜欢使用,也适用于复制/粘贴

protected override void OnPreviewTextInput( System.Windows.Input.TextCompositionEventArgs e )
{
    try
    {
        if ( String.IsNullOrEmpty( SelectedText ) )
        {
            Convert.ToDecimal( this.Text.Insert( this.CaretIndex, e.Text ) );
        }
        else
        {
            Convert.ToDecimal( this.Text.Remove( this.SelectionStart, this.SelectionLength ).Insert( this.SelectionStart, e.Text ) );
        }
    }
    catch
    {
        // mark as handled if cannot convert string to decimal
        e.Handled = true;
    }

    base.OnPreviewTextInput( e );
}

注:未经测试的代码。

0
额外
不适用于复制/粘贴
额外 作者 Kluyg,

为什么不尝试使用KeyDown事件而不是PreviewKeyDown事件。您可以停止那里的无效字符,但所有控制字符都被接受。这似乎适用于我:

private void NumericKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
    bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9);
    bool isNumeric =((e.Key >= Key.D0 && e.Key <= Key.D9) && (e.KeyboardDevice.Modifiers == ModifierKeys.None));
    bool isDecimal = ((e.Key == Key.OemPeriod || e.Key == Key.Decimal) && (((TextBox)sender).Text.IndexOf('.') < 0));
    e.Handled = !(isNumPadNumeric || isNumeric || isDecimal);
}
0
额外

我使用自定义 ValidationRule 来检查文本是否为数字。

public class DoubleValidation : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        if (value is string)
        {
            double number;
            if (!Double.TryParse((value as string), out number))
                return new ValidationResult(false, "Please enter a valid number");
        }

        return ValidationResult.ValidResult;
    }

然后,当我将 TextBox 绑定到数字属性时,我将新的自定义类添加到 Binding.ValidationRules 集合中。在下面的示例中,每当 TextBox.Text 发生更改时都会检查验证规则。


    
        
            
                
            
        
    

0
额外

也可以使用如下转换器:

public class IntegerFormatConverter : IValueConverter
{
    public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int result;
        int.TryParse(value.ToString(), out result);
        return result;
    }

    public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int result;
        int.TryParse(value.ToString(), out result);
        return result;
    }
}
0
额外
不需要转换器 - WPF默认会进行转换。我的问题是关于确保用户从不看到错误,因为他们输入了非数字值。转换器不会阻止他们这样做。
额外 作者 Matt Hamilton,