Числовой ввод данных в WPF
Как вы обрабатываете ввод числовых значений в приложениях WPF?
без элемента управления NumericUpDown я использую текстовое поле и обрабатываю его событие 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;
}
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; }
вот как я это делаю. Он использует регулярное выражение, чтобы проверить, является ли текст, который будет в поле числовым или нет.
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. Используя его немного, я думаю, что мне это может понравиться лучше, чем старый числовой элемент управления 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(); } } }
Я решил упростить ответ, отмеченный как ответ здесь, в основном на 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; }
затем, когда я связываю a
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 (для цветов) или любой другой числовой формат, который вы хотите использовать, также работает с 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. непроверенный код.
добавить в основной раствор, чтобы убедиться, что привязка обновляется до нуля, когда текстовое поле очищается.
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 и просто не допустить, чтобы текстовое поле получало фокус курсора, тем самым создавая свой собственный дешевый элемент управления 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; } }
Это самый простой метод я нашел для этого. Нижняя сторона заключается в том, что контекстное меню текстового поля по-прежнему позволяет нечисловые с помощью вставки. Чтобы быстро решить эту проблему, я просто добавил атрибут / свойство: ContextMenu= " {x: Null}" в текстовое поле, тем самым отключив его. Не идеально, но для моего сценария будет достаточно.
очевидно, вы можете добавить еще несколько ключей/символов в тесте, чтобы включить дополнительные допустимые значения (например,'.', '$' прием...)
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; } }