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





Назовите меня сумасшедшим, но почему бы не поместить кнопки «плюс» и «минус» по обе стороны от элемента управления TextBox и просто предотвратить получение TextBox фокуса курсора, тем самым создав свой собственный дешевый элемент управления NumericUpDown?
В этом случае напишите свой собственный многоразовый элемент управления NUD и используйте его во всем своем приложении!
Сколько времени потребуется, чтобы ввести 4-значный пин-код? Или перевести большую сумму между счетами? Или сложить два больших числа? Или установить номер порта на 8080? Плохо.
Я не могу поверить, что защищаю этот ответ более двух лет спустя, но скорость ввода не имеет к этому никакого отношения - исходный плакат хотел NumericUpDown TextBox, и это сработало бы для этого сценария. Он ничего не сказал о скорости. Если вам нужен 8080, вы просто нажмете вкладку и наберете 8080 на клавиатуре; не особо большое дело. Я беспокоюсь за мозг любого, кто действительно думает, что я говорил, что нажатие кнопки «плюс» тысячи раз было хорошим решением.
@Greg, я согласен с комментарием tags2k здесь, элементы управления NUD эффективно используются при правильном использовании, например, при вводе заказа, где количество по умолчанию может быть равно 1, но пользователь может заказать больше. При принятии решения об использовании NUD возникают два вопроса: 1) Скорее всего, пользователь возьмется за мышь, 2) В большинстве случаев изменений не будет или будет только несколько увеличений / уменьшений.
Как насчет:
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;
}
Это лучше, чем мой подход, поскольку в нем меньше кода и по-прежнему разрешены клавиши управления, такие как клавиши со стрелками, backspace и т. д.
Я просто понял, что ни один из подходов не помешает пользователю вставлять нечисловые символы в элемент управления, но сейчас это не большая проблема. Я отмечу ваш ответ как ответ, потому что, я думаю, он настолько близок, насколько мы можем. Спасибо!
Это не сработает с десятичными знаками. Если пользователь войдет в 5.3. Он также не будет работать с отрицательными числами, потому что «-» не является числом. Легко исправить, но я подумал, что добавлю это предостережение.
e.Handled =! e.Text.ToCharArray (). All (c => Char.IsNumber (c));
e.Handled =! e.Text.All (Char.IsNumber);
Я рекомендую ответ juanagui, поскольку он касается вставки нечисловых данных stackoverflow.com/a/2673131/755404
Вы также можете попробовать использовать проверку данных, если пользователи фиксируют данные перед их использованием. Сделать это, как я обнаружил, было довольно просто и чище, чем возиться с ключами.
В противном случае вы всегда можете отключить вставку!
Да, я все равно обязательно проверю. Мне просто нравится, чтобы пользователь не мог делать ошибок как можно чаще, чтобы почти не было шансов, что они увидят всплывающее окно с ошибкой.
Я использовал присоединенное свойство, чтобы позволить пользователю использовать клавиши вверх и вниз для изменения значений в текстовом поле. Чтобы использовать это, вы просто используете
<TextBox local:TextBoxNumbers.SingleDelta = "1">100</TextBox>
Это на самом деле не решает проблемы проверки, упомянутые в этом вопросе, но он касается того, что я делаю по поводу отсутствия числового элемента управления вверх / вниз. Используя его немного, я думаю, мне он может понравиться больше, чем старый числовой элемент управления вверх / вниз.
Код не идеален, но он справляется со случаями, которые мне нужно было обрабатывать:
Up, стрелка DownShift + Up, стрелка Shift + DownPage Up, Page DownConverter к свойству textCode 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, которые я пробовал, всегда были ошибочными. Ваш работает как шарм, спасибо. :)
Разве нельзя просто использовать что-то вроде следующего?
int numericValue = 0;
if (false == int.TryParse(yourInput, out numericValue))
{
// handle non-numeric input
}
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 по-прежнему позволяет использовать нечисловые значения через Paste. Чтобы быстро решить эту проблему, я просто добавил атрибут / свойство: ContextMenu = "{x: Null}" в TextBox, тем самым отключив его. Не идеально, но для моего сценария этого будет достаточно.
Очевидно, вы можете добавить в тест еще несколько ключей / символов, чтобы включить дополнительные допустимые значения (например, '.', '$' И т. Д.)
К сожалению, в моем решении есть дыры, поскольку символы Oem не преобразуются в строки с одиночными символами (например, '.' = OemPeriod).
Вот как я это делаю. Он использует регулярное выражение, чтобы проверить, является ли текст, который будет в поле, числовым или нет.
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. Но проблема здесь в том, что я не могу добавить минус к существующему числу, потому что предполагается, что новый текст всегда добавляется к старому. Любое предложение?
решение Regex далеко не так хорошо, как метод BindingValidation.
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
Работал у меня.
... пока пользователь не вводит - или.
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. Значение может быть просто "."
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. Значение может быть просто ".".
Добавьте это в основное решение, чтобы убедиться, что привязка обновляется до нуля при очистке текстового поля.
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?
Я решил упростить ответ, помеченный как ответ здесь, в основном до двух строк с использованием выражения LINQ.
e.Handled = !e.Text.All(Char.IsNumber);
base.OnPreviewTextInput(e);
Это красиво и аккуратно. Но как заставить его принять "." и "-"?
e.Text.All(cc => Char.IsNumber(cc) || cc == '.' || cc == '-')?
К сожалению, OnPreviewTextInput не вызывается, когда пользователь вводит пробел. Это можно было бы обработать отдельно, также переопределив OnPreviewKeyDown, в любом случае пользователь все равно сможет вставлять нечисловые символы. PS: вы можете улучшить свой ответ, написав полный метод: protected override void OnPreviewTextInput(TextCompositionEventArgs 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 );
}
N.B. Непроверенный код.
Почему бы вам просто не попробовать использовать событие 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);
}
Я использую специальный 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>
Также можно использовать конвертер, например:
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 выполнит преобразование по умолчанию. Мой вопрос был больше о том, чтобы пользователь никогда не увидел ошибку, потому что он ввел нечисловое значение. Конвертер не мешает им это делать.
Объединив идеи из некоторых из этих ответов, я создал 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);
}
}
Скорость ввода данных. У нас есть операторы ввода данных, которые вводят данные с помощью клавиатуры (и часто просто цифровой клавиатуры), поэтому для них непрактично дотянуться до мыши на полпути через экран ввода.