I am trying to filter the input to a WPF TextBox
to prevent the user from entering non-numerical strings. I have configured PreviewKeyDown
and am checking the characters entered with the code from this question to convert the key codes to characters. Everything works as expected except when the user enters a period. The code does detect a period was entered, yet when I return from PreviewKeyDown
with setting KeyEventArgs
's Handled
to false, it doesn't allow the period to be entered.
XAML
<TextBox PreviewKeyDown="TextBox_PreviewKeyDown">
<TextBox.Text>
<Binding Source="{StaticResource SomeObject}" Path="SomePath" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MyValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
C#
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
char character = GetCharFromKey(e.Key);
e.Handled = false;
if (character >= '0' && character <= '9')
return;
if (character == '.')
return;
switch(e.Key)
{
case Key.Delete:
case Key.Back:
return;
}
e.Handled = true;
}
Can't you handle the PreviewTextInput
event? Something like this:
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
string str = ((TextBox)sender).Text + e.Text;
decimal i;
e.Handled = !decimal.TryParse(str, System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.CultureInfo.InvariantCulture, out i);
}
XAML:
<TextBox Text="{Binding SomePath}" PreviewTextInput="TextBox_PreviewTextInput" />
Edit: The problem with using a an UpdateSourceTrigger
of PropertyChanged
is that the string "5." gets converted to 5M
and that's the value that you see in the TextBox
. The "5." string is not stored somewhere.
You could possible overcome this by using a converter that keeps track of the latest known string:
public class DecimalToStringConverter : IValueConverter
{
private string _lastConvertedValue;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return _lastConvertedValue ?? value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string str = value?.ToString();
decimal d;
if (decimal.TryParse(str, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out d))
{
_lastConvertedValue = str;
return d;
}
_lastConvertedValue = null;
return Binding.DoNothing;
}
}
XAML:
<TextBox PreviewTextInput="TextBox_PreviewTextInput">
<TextBox.Text>
<Binding Path="SomePath" UpdateSourceTrigger="PropertyChanged">
<Binding.Converter>
<local:DecimalToStringConverter />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>