代码之家  ›  专栏  ›  技术社区  ›  Matt Hamilton

WPF中的数字数据输入

  •  62
  • Matt Hamilton  · 技术社区  · 17 年前

    没有NumericUpDown控件,我一直在使用一个文本框,并用下面的代码处理它的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;
    }
    
    17 回复  |  直到 6 年前
        1
  •  58
  •   Markus Safar    9 年前

    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;
    }
    
        2
  •  14
  •   Markus Safar    9 年前

    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}"/>
    

        3
  •  11
  •   Markus Safar    9 年前

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

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

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

    • Up Down
    • Shift + Up Shift + Down
    • Page Up , Page Down
    • 结合 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();
            }
        }
    }
    
        4
  •  11
  •   Markus Safar    9 年前

    e.Handled = !e.Text.All(Char.IsNumber);
    base.OnPreviewTextInput(e);
    
        5
  •  4
  •   Jroonk    14 年前

    为什么不尝试使用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
  •  4
  •   Markus Safar    9 年前

    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
  •  3
  •   Markus Safar    9 年前
    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);
            }
        }
    }
    

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

        8
  •  3
  •   Markus Safar    9 年前

    结合这些答案中的一些想法,我创建了一个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
  •  2
  •   VolkerK    17 年前

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

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

        10
  •  2
  •   Community Mohan Dere    8 年前

    我的版本 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
  •  2
  •   akjoshi HCP    13 年前

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

    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();
            }
        }
    }
    
        12
  •  1
  •   tags2k    17 年前

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

        13
  •  1
  •   Audaxis Audaxis    16 年前
    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;
        }
    }
    

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

        14
  •  1
  •   sth    15 年前
    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
  •  1
  •   Markus Safar    9 年前

    int numericValue = 0;
    
    if (false == int.TryParse(yourInput, out numericValue))
    {
        // handle non-numeric input
    }
    
        16
  •  1
  •   Markus Safar    9 年前
    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;
            }
        }
    }
    
        17
  •  1
  •   Markus Safar    9 年前

    也可以使用以下转换器:

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