Сведения о вопросе

Holish

16:03, 1st July, 2020

Теги

c#   wpf    

Числовой ввод данных в WPF

Просмотров: 491   Ответов: 17

Как вы обрабатываете ввод числовых значений в 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;
}



  Сведения об ответе

darknet

18:03, 1st July, 2020

Как насчёт:

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


  Сведения об ответе

piter

18:03, 1st July, 2020

Вот как я это делаю. Он использует регулярное выражение, чтобы проверить, является ли текст, который будет в поле числовым или нет.

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

Обратите внимание, что вы можете использовать это в пользовательских свойствах тоже, все, что вам нужно сделать, это создать исключение, если значение в поле недопустимо, и элемент управления будет выделен красной рамкой. Если вы нажмете на верхнюю правую часть красной границы, то появится сообщение об исключении.


  Сведения об ответе

SILA

18:03, 1st July, 2020

Я решил упростить ответ, помеченный как ответ здесь, в основном до 2 строк, используя выражение LINQ.

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


  Сведения об ответе

PIRLO

18:03, 1st July, 2020

Я использовал вложенное свойство, чтобы позволить пользователю использовать клавиши вверх и вниз для изменения значений в текстовом поле. Чтобы использовать его, вы просто используете

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

Это на самом деле не решает проблемы проверки, которые упоминаются в этом вопросе, но это касается того, что я делаю с отсутствием числового элемента управления up/down. Используя его немного, я думаю, что он действительно может понравиться мне больше, чем старый числовой элемент управления up/down.

Код не идеален, но он справляется с делами, которые мне были нужны:

  • Up стрелка, Down стрелка
  • Стрелка Shift + Up , Shift + Down стрелка
  • Page Up , Page Down
  • Привязка Converter к свойству text

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


  Сведения об ответе

PAGE

18:03, 1st July, 2020

Я использую пользовательский 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>


  Сведения об ответе

dump

18:03, 1st July, 2020

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

Кроме того, вы можете настроить поведение синтаксического анализа, предоставив соответствующие свойства зависимостей.


  Сведения об ответе

LIZA

18:03, 1st July, 2020

Почему бы вам просто не попробовать использовать событие 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);
}


  Сведения об ответе

fo_I_K

18:03, 1st July, 2020

Объединив идеи нескольких из этих ответов, я создал 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);
    }
}


  Сведения об ответе

SSESION

18:03, 1st July, 2020

Моя версия Arcturus answer, может изменить метод преобразования, используемый для работы с int / uint / decimal / byte (для цветов) или любым другим числовым форматом, который вы хотите использовать, также работает с copy / paste

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

N.B. Непроверенный код.


  Сведения об ответе

SKY

18:03, 1st July, 2020

Добавьте это в основное решение, чтобы убедиться, что привязка обновляется до нуля, когда textbox очищается.

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


  Сведения об ответе

ASSembler

18:03, 1st July, 2020

Вы также можете попробовать использовать проверку данных, если пользователи фиксируют данные перед их использованием. Делать это оказалось довольно просто и чище, чем возиться с ключами.

В противном случае, вы всегда можете отключить вставку тоже!


  Сведения об ответе

KOMP

18:03, 1st July, 2020

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

Работать на меня.


  Сведения об ответе

9090

18:03, 1st July, 2020

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


  Сведения об ответе

DAAA

18:03, 1st July, 2020

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 все еще позволяет использовать нечисловые символы через вставку. Чтобы быстро решить эту проблему, я просто добавил attribute/property: ContextMenu="{x:Null}" к TextBox, тем самым отключив его. Не идеально, но для моего сценария этого будет достаточно.

Очевидно, что вы можете добавить еще несколько ключей/символов в тесте, чтобы включить дополнительные приемлемые значения (например,'.', '$' прием...)


  Сведения об ответе

DAAA

18:03, 1st July, 2020

Назовите меня сумасшедшим, но почему бы не поставить плюсовые и минусовые кнопки по обе стороны от элемента управления TextBox и просто не допустить, чтобы TextBox получил фокус курсора, тем самым создав свой собственный дешевый элемент управления NumericUpDown?


  Сведения об ответе

JUST___

18:03, 1st July, 2020

Вы не можете просто использовать что-то вроде следующего?

int numericValue = 0;

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


  Сведения об ответе

ITSME

18:03, 1st July, 2020

Можно также использовать такой конвертер, как:

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться