سؤال

كيف تتعامل مع إدخال القيم الرقمية في تطبيقات 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;
}
هل كانت مفيدة؟

المحلول

ماذا عن:

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

نصائح أخرى

هذه هي الطريقة التي أفعل ذلك.يستخدم تعبيرًا عاديًا للتحقق مما إذا كان النص الموجود في المربع رقميًا أم لا.

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

لاحظ أنه يمكنك استخدام هذا على الخصائص المخصصة أيضًا، كل ما عليك فعله هو طرح استثناء إذا كانت القيمة الموجودة في المربع غير صالحة وسيتم تمييز عنصر التحكم بحد أحمر.إذا نقرت على الجزء العلوي الأيمن من الحد الأحمر، فستنبثق رسالة الاستثناء.

لقد كنت أستخدم خاصية مرفقة للسماح للمستخدم باستخدام المفتاحين لأعلى ولأسفل لتغيير القيم في مربع النص.لاستخدامه، ما عليك سوى استخدام

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

قررت تبسيط الرد الذي تم وضع علامة عليه كإجابة هنا إلى سطرين بشكل أساسي باستخدام تعبير LINQ.

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

أنا استخدم العرف 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>

لماذا لا تحاول فقط استخدام حدث 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);
}
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);
        }
    }
}

بالإضافة إلى ذلك، يمكنك تخصيص سلوك التحليل من خلال توفير خصائص التبعية المناسبة.

من خلال الجمع بين الأفكار من عدد قليل من هذه الإجابات، قمت بإنشاء 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);
    }
}

يمكنك أيضًا تجربة استخدام التحقق من صحة البيانات إذا قام المستخدمون بإرسال البيانات قبل استخدامها.لقد وجدت أن القيام بذلك كان بسيطًا وأنظف إلى حد ما من العبث بالمفاتيح.

بخلاف ذلك، يمكنك دائمًا تعطيل اللصق أيضًا!

نسختي من السماك القطبية الإجابة، يمكن تغيير طريقة التحويل المستخدمة للعمل مع 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 );
}

ملحوظة:كود لم يتم اختباره

أضف هذا إلى الحل الرئيسي للتأكد من تحديث الارتباط إلى الصفر عند مسح مربع النص.

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

اتصل بي بالجنون، ولكن لماذا لا تضع أزرار زائد وناقص على جانبي عنصر تحكم TextBox وتمنع ببساطة TextBox من تلقي تركيز المؤشر، وبالتالي إنشاء عنصر تحكم NumericUpDown رخيص خاص بك؟

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 وبالتالي تعطيله.ليست مثالية ولكن بالنسبة للسيناريو الخاص بي سيكون كافيا.

من الواضح أنه يمكنك إضافة عدد قليل من المفاتيح/الأحرف في الاختبار لتضمين قيم إضافية مقبولة (على سبيل المثال.'.'، '$' الخ...)

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

عملت بالنسبة لي.

ألا يمكنك فقط استخدام شيء مثل ما يلي؟

int numericValue = 0;

if (false == int.TryParse(yourInput, out numericValue))
{
    // handle non-numeric input
}
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;
        }
    }
}

يمكن أيضًا استخدام محول مثل:

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;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top