Цифровой ввод данных в WPF

Как вы обрабатываете запись числовых значений в приложениях WPF?

Без элемента управления NumericUpDown я использовал TextBox и обрабатывал его событие PreviewKeyDown с приведенным ниже кодом, но он довольно уродлив.

Кто-нибудь нашел более грациозный способ получить числовые данные от пользователя, не полагаясь на сторонний элемент управления?

private void NumericEditPreviewKeyDown(object sender, KeyEventArgs e) { bool isNumPadNumeric = (e.Key >= Key.NumPad0 && 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 теперь есть намного лучший способ сделать это. Если ваш элемент управления связан с свойством, все, что вам нужно сделать, это немного изменить свой оператор привязки. Для привязки используйте следующее:

  

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

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

 100 

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

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

  • Стрелка Up стрелка Down
  • Shift + Up стрелка Shift + Up стрелка Shift + Down стрелка 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(); } } } 

Я решил упростить ответ, помеченный как ответ здесь, в основном 2 строки, используя выражение 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 к числовому свойству, я добавляю новый пользовательский class в коллекцию Binding.ValidationRules . В приведенном ниже примере правило проверки проверяется каждый раз, TextBox.Text изменяется TextBox.Text .

          

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

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

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

Моя версия ответа 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 Неподтвержденный код.

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

 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 по-прежнему позволяет использовать нечисловые значения через Paste. Чтобы быстро решить эту проблему, я просто добавил атрибут / свойство: 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; } } 
  • Confused with wpf ComboBox DisplayMemberPath, SelectedValue и SelectedValuePath
  • Давайте будем гением компьютера.