studio - Entrada de datos numéricos en WPF
visual studio wpf (17)
¿Cómo maneja la entrada de valores numéricos en las aplicaciones de WPF?
Sin un control NumericUpDown, he estado usando un TextBox y manejo su evento PreviewKeyDown con el siguiente código, pero es bastante feo.
¿Alguien ha encontrado una forma más elegante de obtener datos numéricos del usuario sin depender de un control de terceros?
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;
}
¿No puedes usar algo como lo siguiente?
int numericValue = 0;
if (false == int.TryParse(yourInput, out numericValue))
{
// handle non-numeric input
}
¿Por qué no intenta usar el evento KeyDown en lugar del evento PreviewKeyDown? Puede detener los caracteres no válidos allí, pero se aceptan todos los caracteres de control. Esto parece funcionar para mí:
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);
}
Agregue esto a la solución principal para asegurarse de que el enlace se actualice a cero cuando se borre el cuadro de texto.
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();
}
}
}
Así es como lo hago. Utiliza una expresión regular para comprobar si el texto que estará en el cuadro es numérico o no.
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");
}
Ahora hay una forma mucho mejor de hacerlo en WPF y Silverlight. Si su control está ligado a una propiedad, todo lo que tiene que hacer es cambiar un poco su declaración vinculante. Use lo siguiente para su encuadernación:
<TextBox Text="{Binding Number, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"/>
Tenga en cuenta que también puede usar esto en propiedades personalizadas, todo lo que tiene que hacer es lanzar una excepción si el valor en el cuadro no es válido y el control se resaltará con un borde rojo. Si hace clic en la esquina superior derecha del borde rojo, aparecerá el mensaje de excepción.
Combinando las ideas de algunas de estas respuestas, he creado un NumericTextBox que
- Maneja decimales
- ¿Alguna validación básica para garantizar que se ingrese ''-'' o ''.'' es válida
- Maneja valores pegados
Por favor, no dude en actualizar si puede pensar en cualquier otra lógica que deba incluirse.
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);
}
}
Decidí simplificar la respuesta marcada como respuesta aquí básicamente 2 líneas usando una expresión LINQ.
e.Handled = !e.Text.All(Char.IsNumber);
base.OnPreviewTextInput(e);
He estado usando una propiedad adjunta para permitir que el usuario use las teclas hacia arriba y hacia abajo para cambiar los valores en el cuadro de texto. Para usarlo, solo usa
<TextBox local:TextBoxNumbers.SingleDelta="1">100</TextBox>
En realidad, esto no aborda los problemas de validación a los que se hace referencia en esta pregunta, pero aborda lo que hago acerca de no tener un control numérico de arriba / abajo. Utilicándolo por un tiempo, creo que en realidad podría gustarme más que el antiguo control numérico de subida / bajada.
El código no es perfecto, pero maneja los casos que necesitaba para manejar:
- Flecha
Down
Up
, flechaDown
-
Shift + Up
flechaShift + Up
,Shift + Down
flechaShift + Down
-
Page Up
,Page Down
-
Converter
enlace en la propiedad de texto
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();
}
}
}
Llámame loco, pero ¿por qué no poner botones más y menos a cada lado del control TextBox y simplemente evitar que TextBox reciba el foco del cursor, creando así tu propio control numérico barato?
Mi versión de respuesta de Arcturus , puede cambiar el método de conversión utilizado para trabajar con int / uint / decimal / byte (para colores) o cualquier otro formato numérico que desee utilizar, también funciona con copiar / pegar
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 Código no probado.
Qué tal si:
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;
}
También puede intentar usar la validación de datos si los usuarios confirman los datos antes de usarlos. Hacer eso que encontré fue bastante simple y más limpio que juguetear con las teclas.
De lo contrario, siempre puedes deshabilitar Pegar también!
Utilizo una ValidationRule
personalizada para verificar si el texto es numérico.
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;
}
Luego, cuando enlace un TextBox
a una propiedad numérica, agrego la nueva clase personalizada a la colección Binding.ValidationRules
. En el ejemplo siguiente, la regla de validación se verifica cada vez que se cambia TextBox.Text
.
<TextBox>
<TextBox.Text>
<Binding Path="MyNumericProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DoubleValidation/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
También puede usar un convertidor como:
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;
}
}
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
Trabajó para mi.
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;
}
}
Esta es la técnica más fácil que he encontrado para lograr esto. El inconveniente es que el menú contextual de TextBox todavía permite que no sean numéricos a través de Pegar. Para resolver esto rápidamente, simplemente agregué el atributo / propiedad: ContextMenu = "{x: Null}" al TextBox, inhabilitándolo. No es ideal, pero para mi caso será suficiente.
Obviamente, podría agregar algunas claves / caracteres más en la prueba para incluir valores adicionales aceptables (por ejemplo, ''.'', ''$'', Etc.)
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);
}
}
}
Además, puede personalizar el comportamiento de análisis proporcionando propiedades de dependencia adecuadas.
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;
}
}
}