在WPF中的数字数据input
你如何处理WPF应用程序中的数值的input?
没有一个NumericUpDown控件,我一直在使用一个TextBox,并用下面的代码处理它的PreviewKeyDown事件,但是这非常难看。
有没有人find一个更优雅的方式来获取用户的数字数据,而不依赖于第三方控制?
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; }
怎么样:
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; }
这是我如何做到的。 它使用正则expression式来检查框中的文本是否为数字。
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}"/>
请注意,您也可以在自定义属性上使用此属性,如果框中的值无效并且控件将以红色边框突出显示,则只需要抛出exception。 如果您单击红色边框的右上angular,则会popupexception消息。
我决定简化标记为这里回答的答复,基本上是使用LINQexpression式的两行。
e.Handled = !e.Text.All(Char.IsNumber); base.OnPreviewTextInput(e);
我一直在使用附加的属性来允许用户使用上下键来改变文本框中的值。 要使用它,你只需使用
<TextBox local:TextBoxNumbers.SingleDelta="1">100</TextBox>
这实际上并没有解决在这个问题中提到的validation问题,但它解决了我没有数字上/下控制的问题。 稍微使用它,我想我可能会喜欢它比旧的数字上/下控制更好。
代码不完美,但它处理我需要处理的情况:
-
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(); } } }
我使用自定义的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
更改时都会检查validation规则。
<TextBox> <TextBox.Text> <Binding Path="MyNumericProperty" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:DoubleValidation/> </Binding.ValidationRules> </Binding> </TextBox.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); } } }
此外,您可以通过提供适当的依赖项属性来自定义parsing行为。
为什么不尝试使用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); }
结合几个这些答案的想法,我创build了一个NumericTextBox
- 处理小数
- 做一些基本的validation,以确保input的“ – ”或“。” 已validation
- 处理粘贴的值
请随时更新,如果你能想到任何其他的逻辑应该包括在内。
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); } }
如果用户在使用数据之前提交数据,也可以尝试使用数据validation。 这样做,我发现相当简单,清洁比用钥匙摆弄。
否则,您总是可以禁用粘贴!
我的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未经testing的代码。
将其添加到主要解决scheme以确保在清除文本框时将绑定更新为零。
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(); } } }
叫我疯了,但为什么不把加号和减号button在文本框控件的任何一边,只是防止文本框接收光标焦点,从而创build自己的便宜的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的上下文菜单仍然允许通过粘贴非数字。 为了快速解决这个问题,我简单地将属性/属性:ContextMenu =“{x:Null}”添加到文本框中,从而禁用它。 不理想,但对我的情况,这将足够。
显然你可以在testing中添加更多的键/字符以包含额外的可接受的值(例如'。','$'等等)
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; } }