Ввод числовых данных в 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;
}
8.08.2008 00:37:55
17 ОТВЕТОВ
РЕШЕНИЕ

Как насчет:

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;
}
57
7.02.2016 17:48:37
Я только что понял, что ни один из подходов не помешает пользователю вставлять нецифровые символы в элемент управления, но сейчас это не так уж важно. Я отмечу ваш ответ как ответ, потому что он настолько близок, насколько мы можем, я думаю. Спасибо!
Matt Hamilton 2.04.2009 00:00:33
Это потерпит неудачу на десятичных дробях. Если пользователь вводит 5.3. Он также потерпит неудачу на отрицательных числах, потому что "-" не число. Легко исправить, но я просто подумал, что я бы добавил это предостережение.
Kelly 18.03.2010 14:56:34
e.Handled =! e.Text.ToCharArray (). All (c => Char.IsNumber (c));
si618 26.07.2010 05:19:58
Почему не Double.TryParse?
Louis Rhys 29.06.2011 10:26:05
e.Handled =! e.Text.All (Char.IsNumber);
Olson.dev 6.12.2011 20:49:30

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

0
8.08.2008 17:27:04
Скорость ввода данных. У нас есть операторы ввода данных, которые вводят данные, используя клавиатуру (и часто просто цифровую клавиатуру), поэтому для них нецелесообразно тянуть мышь на полпути через экран ввода.
Matt Hamilton 1.04.2009 23:59:22
В этом случае напишите свой собственный многократно используемый элемент управления NUD и используйте его в своем приложении!
RQDQ 15.10.2010 20:33:55
Сколько времени потребуется, чтобы ввести 4-значный пин-код? Или перевести большую сумму между счетами? Или добавить два больших числа? Или установить номер порта на 8080? Плохой.
Greg Sansom 19.01.2011 01:54:15
Я не могу поверить, что защищаю этот ответ в течение 2 лет, но скорость ввода не имеет к этому никакого отношения - первоначальный постер хотел иметь текстовое поле NumericUpDown, и это сработало бы для этого сценария. Он ничего не сказал о скорости. Если вы хотите 8080, просто нажмите клавишу Tab и наберите 8080 на клавиатуре; не очень большое дело. Я волнуюсь за мозг любого, кто действительно думает, что я говорил, что нажатие кнопки «плюс» тысячи раз было хорошим решением.
tags2k 20.01.2011 16:11:19
@ Грег, я согласен с комментарием tags2k здесь, элементы управления NUD эффективно используются при правильном использовании, например, при вводе заказа, где qty по умолчанию может быть 1, но пользователь может заказать больше. При решении использовать NUD возникает два вопроса: 1) будет ли пользователь, скорее всего, держать руку на мыши, 2) в большинстве случаев, скорее всего, не будет никаких изменений или только несколько приращений / уменьшений.
Brett Ryan 20.07.2011 07:43:44

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

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

1
12.08.2008 05:14:52
Да, я все равно буду проверять. Мне просто нравится, чтобы пользователь не мог совершать как можно больше ошибок, чтобы не было никаких шансов, что он увидит всплывающее окно с ошибкой.
Matt Hamilton 2.04.2009 00:01:26

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

<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();
        }
    }
}
10
7.02.2016 17:50:05
Это отличный кусок кода. Различные элементы управления NUD, которые я пробовал, всегда были с ошибками. Ваш работает как шарм, спасибо. :)
Echilon 11.01.2010 14:17:32

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

int numericValue = 0;

if (false == int.TryParse(yourInput, out numericValue))
{
    // handle non-numeric input
}
0
7.02.2016 17:51:08
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, тем самым отключив его. Не идеально, но для моего сценария этого будет достаточно.

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

0
18.02.2009 17:11:53
К сожалению, в моем решении есть дыры, поскольку символы Oem не преобразуются в строки с одиночными символами (например, '.' = OemPeriod).
Audaxis 19.02.2009 14:48:15

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

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

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

13
7.02.2016 17:48:58
Мне нравится решение Regex. Но проблема здесь в том, что я не могу добавить минус к существующему номеру, поскольку предполагается, что новый текст всегда добавляется к старому. Любое предложение?
newman 2.01.2011 19:50:03
решение Regex не так хорошо, как метод BindingValidation.
Eric 28.01.2011 21:49:31
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
24.01.2010 08:16:14
... до тех пор, пока пользователь не введет - или.
Greg Sansom 19.01.2011 01:50:51
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
7.02.2016 17:51:18
Это не будет работать, потому что e.Text содержит только добавленный текст, а не все содержимое TextBox. Значение может быть просто "."
Greg Sansom 19.01.2011 01:54:55
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);
        }
    }
}

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

2
7.02.2016 17:50:39
Это не будет работать, потому что e.Text содержит только добавленный текст, а не все содержимое TextBox. Значение может быть просто ".".
Greg Sansom 19.01.2011 01:41:56

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

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();
        }
    }
}
1
8.06.2012 07:01:54
Интересно. Нужно ли заключать его в вызов IsDataBound?
Matt Hamilton 4.05.2010 10:48:56

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

e.Handled = !e.Text.All(Char.IsNumber);
base.OnPreviewTextInput(e);
10
7.02.2016 17:50:14
Это красиво и аккуратно. Но как мне заставить его принять "." и "-"?
newman 2.01.2011 19:53:10
e.Text.All(cc => Char.IsNumber(cc) || cc == '.' || cc == '-')?
user7116 3.02.2011 18:00:45
К сожалению, OnPreviewTextInput не вызывается, когда пользователь вводит пробел. Это может быть отдельно обработано путем переопределения OnPreviewKeyDown, в любом случае пользователь все равно сможет вставлять нечисловые символы. PS: вы могли бы улучшить свой ответ, написав полный метод:protected override void OnPreviewTextInput(TextCompositionEventArgs e) { ... }
Gobe 8.02.2016 20:26:25

Моя версия ответа 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 );
}

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

1
23.05.2017 12:09:02

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

Я использую обычай, 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;
    }

Затем, когда я связываю a TextBoxс числовым свойством, я добавляю новый пользовательский класс в Binding.ValidationRulesколлекцию. В приведенном ниже примере правило проверки проверяется при каждом TextBox.Textизменении.

<TextBox>
    <TextBox.Text>
        <Binding Path="MyNumericProperty" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:DoubleValidation/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>
3
7.02.2016 17:50:26

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

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
7.02.2016 17:51:42
Не нужен конвертер - WPF сделает конвертацию по умолчанию. Мой вопрос был больше о том, чтобы пользователь никогда не видел ошибку, потому что он ввел нечисловое значение. Конвертер не мешает им делать это.
Matt Hamilton 8.08.2012 05:46:09

Комбинируя идеи из нескольких ответов, я создал 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);
    }
}
2
7.02.2016 17:50:56