【问题标题】:Numeric Data Entry in WPFWPF 中的数值数据输入
【发布时间】:2010-09-05 13:15:14
【问题描述】:

您如何处理 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;
}

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    怎么样:

    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;
    }
    

    【讨论】:

    • 这比我的方法更好,因为它的代码更少,并且仍然允许控制键,如箭头键和退格键等。
    • 我刚刚意识到这两种方法都不会阻止用户将非数字字符粘贴到控件中,但这不是一个大问题。我会将您的回复标记为答案,因为我认为这是我们能得到的最接近的答案。谢谢!
    • 这将在小数上失败。如果用户输入 5.3.它也会在负数上失败,因为“-”不是数字。很容易修复,但我只是想我会在那儿小心翼翼。
    • e.Handled = !e.Text.ToCharArray().All(c => Char.IsNumber(c));
    • e.Handled = !e.Text.All(Char.IsNumber);
    【解决方案2】:

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

    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 中有更好的方法来执行此操作。如果您的控件绑定到一个属性,您所要做的就是稍微更改您的绑定语句。使用以下内容进行绑定:

    <TextBox Text="{Binding Number, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"/>
    

    请注意,您也可以在自定义属性上使用它,如果框中的值无效并且控件将以红色边框突出显示,您所要做的就是抛出异常。点击右上角红色边框会弹出异常信息。

    【讨论】:

    • 我喜欢正则表达式解决方案。但这里的问题是我不能在现有数字上添加减号,因为它假定新文本总是附加到旧文本中。有什么建议吗?
    • Regex 解决方案不如 BindingValidation 方法好。
    【解决方案3】:

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

    <TextBox local:TextBoxNumbers.SingleDelta="1">100</TextBox>
    

    这实际上并没有解决这个问题中提到的验证问题,但它解决了我在没有数字向上/向下控件时所做的事情。使用它一点点,我想我实际上可能比旧的数字上/下控件更喜欢它。

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

    • Up箭头,Down箭头
    • Shift + Up箭头,Shift + Down箭头
    • Page Up, Page Down
    • 在 text 属性上绑定Converter

    Code behind

    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();
            }
        }
    }
    

    【讨论】:

    • 那是一段很棒的代码。我尝试过的各种 NUD 控件一直都是错误的。你的作品就像魅力一样,谢谢。 :)
    【解决方案4】:

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

    e.Handled = !e.Text.All(Char.IsNumber);
    base.OnPreviewTextInput(e);
    

    【讨论】:

    • 这很好,很整洁。但是我如何让它接受“。”和“-”?
    • e.Text.All(cc =&gt; Char.IsNumber(cc) || cc == '.' || cc == '-') ?
    • 不幸的是,当用户输入空格时,不会调用 OnPreviewTextInput。这可以通过覆盖 OnPreviewKeyDown 来单独处理,无论如何用户仍然可以粘贴非数字字符。 PS.:您可以通过编写完整方法来改进您的答案:protected override void OnPreviewTextInput(TextCompositionEventArgs e) { ... }
    【解决方案5】:

    您为什么不尝试使用 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);
    }
    

    【讨论】:

      【解决方案6】:

      我使用自定义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 更改时都会检查验证规则。

      <TextBox>
          <TextBox.Text>
              <Binding Path="MyNumericProperty" UpdateSourceTrigger="PropertyChanged">
                  <Binding.ValidationRules>
                      <local:DoubleValidation/>
                  </Binding.ValidationRules>
              </Binding>
          </TextBox.Text>
      </TextBox>
      

      【讨论】:

        【解决方案7】:
        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);
                }
            }
        }
        

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

        【讨论】:

        • 这不起作用,因为 e.Text 只包含添加的文本,而不是整个 TextBox 内容。该值可能只是"."
        【解决方案8】:

        结合其中一些答案的想法,我创建了一个 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);
            }
        }
        

        【讨论】:

          【解决方案9】:

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

          否则,您也可以随时禁用粘贴!

          【讨论】:

          • 是的,无论如何我肯定会进行验证。我只是想尽可能地防止用户犯错误,这样他们就几乎没有机会看到错误弹出窗口。
          【解决方案10】:

          Arcturus 答案的我的版本,可以更改用于使用 int / uint / decimal / byte(用于颜色)或您喜欢使用的任何其他数字格式的转换方法,也适用于复制/粘贴

          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 );
          }
          

          注意未经测试的代码。

          【讨论】:

            【解决方案11】:

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

            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();
                    }
                }
            }
            

            【讨论】:

            • 有趣。是否需要包装在 IsDataBound 调用中?
            【解决方案12】:

            说我疯了,但为什么不在 TextBox 控件的任一侧放置加号和减号按钮并简单地阻止 TextBox 接收光标焦点,从而创建您自己的廉价 NumericUpDown 控件?

            【讨论】:

            • 数据输入速度。我们有数据输入操作员,他们在使用键盘(通常只是数字小键盘)时敲击数据,因此他们在输入屏幕的一半时伸手去拿鼠标是不切实际的。
            • 在这种情况下,编写您自己的可重复使用的 NUD 控件并在整个应用程序中使用它!
            • 输入 4 位密码需要多长时间?还是账户间大额转账?或者添加两个大数?或者将端口号设置为8080?不好。
            • 我不敢相信我在 2 年多的时间里为这个回应辩护,但进入速度与它无关 - 原始发布者想要一个 NumericUpDown 文本框,这适用于那种情况。他对速度只字未提。如果您想要 8080,您只需按 Tab 键并在键盘上输入 8080;没什么大不了的。我担心那些真正认为我在说点击加号按钮数千次是一个很好的解决方案的人的大脑。
            • @Greg,我同意 tags2k 在这里的评论,NUD 控件在使用得当时可以有效使用,例如默认数量可能为 1 的订单输入,但用户可以选择订购更多。决定使用 NUD 时有两个问题,1) 用户最有可能将手放在鼠标上,2) 在大多数情况下,可能没有变化或只有少量增量/减量。
            【解决方案13】:
            private void txtNumericValue_PreviewKeyDown(object sender, KeyEventArgs e)
            {
                KeyConverter converter = new KeyConverter();
            
                string key = converter.ConvertToString(e.Key);
            
                if (key != null && key.Length == 1)
                {
                    e.Handled = Char.IsDigit(key[0]) == false;
                }
            }
            

            这是我发现的最简单的技术。不利的一面是 TextBox 的上下文菜单仍然允许通过粘贴来输入非数字。为了快速解决这个问题,我只是将属性/属性:ContextMenu="{x:Null}" 添加到 TextBox 从而禁用它。不理想,但对于我的场景来说就足够了。

            显然,您可以在测试中添加更多键/字符以包含其他可接受的值(例如,'.'、'$' 等...)

            【讨论】:

            • 遗憾的是,我的解决方案存在漏洞,因为 Oem 字符未转换为具有单个字符的字符串(例如 '.' = OemPeriod)。
            【解决方案14】:
            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
            

            为我工作。

            【讨论】:

            • ...只要用户不输入 - 或 .
            【解决方案15】:

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

            int numericValue = 0;
            
            if (false == int.TryParse(yourInput, out numericValue))
            {
                // handle non-numeric input
            }
            

            【讨论】:

              【解决方案16】:
              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;
                      }
                  }
              }
              

              【讨论】:

              • 这不起作用,因为 e.Text 只包含添加的文本,而不是整个 TextBox 内容。该值可能只是“。”
              【解决方案17】:

              也可以使用如下转换器:

              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;
                  }
              }
              

              【讨论】:

              • 不需要转换器 - WPF 默认会进行转换。我的问题更多是关于确保用户永远不会看到错误,因为他们输入了非数字值。转换器不会阻止他们这样做。
              猜你喜欢
              • 2010-12-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多