Reputation: 774
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;
}
Upvotes: 1
Views: 2094
Reputation: 169210
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>
Upvotes: 1
Reputation: 12276
I have a behaviour I use for this, I think it's based on something I got off the web. You could just use it as is, or work out why your version isn't working. Note the code handles pasting in though.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
namespace UILib
{
public class TextBoxDecimalRangeBehaviour : Behavior<TextBox>
{
public string EmptyValue { get; set; } = "0";
public double Minimum
{
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(TextBoxDecimalRangeBehaviour), new PropertyMetadata(0.0));
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(TextBoxDecimalRangeBehaviour), new PropertyMetadata(10.0));
public int MaxInteger
{
get { return (int)GetValue(MaxIntegerProperty); }
set { SetValue(MaxIntegerProperty, value); }
}
public static readonly DependencyProperty MaxIntegerProperty =
DependencyProperty.Register("MaxInteger", typeof(int), typeof(TextBoxDecimalRangeBehaviour), new PropertyMetadata(1));
public int MaxDecimals
{
get { return (int)GetValue(MaxDecimalsProperty); }
set { SetValue(MaxDecimalsProperty, value); }
}
public static readonly DependencyProperty MaxDecimalsProperty =
DependencyProperty.Register("MaxDecimals", typeof(int), typeof(TextBoxDecimalRangeBehaviour), new PropertyMetadata(2));
/// <summary>
/// Attach our behaviour. Add event handlers
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTextInput += PreviewTextInputHandler;
AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler;
DataObject.AddPastingHandler(AssociatedObject, PastingHandler);
}
/// <summary>
/// Deattach our behaviour. remove event handlers
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewTextInput -= PreviewTextInputHandler;
AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler;
DataObject.RemovePastingHandler(AssociatedObject, PastingHandler);
}
void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
string text;
if (this.AssociatedObject.Text.Length < this.AssociatedObject.CaretIndex)
text = this.AssociatedObject.Text;
else
{
// Remaining text after removing selected text.
string remainingTextAfterRemoveSelection;
text = TreatSelectedText(out remainingTextAfterRemoveSelection)
? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text)
: AssociatedObject.Text.Insert(this.AssociatedObject.CaretIndex, e.Text);
}
e.Handled = !ValidateText(text);
}
/// <summary>
/// PreviewKeyDown event handler
/// </summary>
void PreviewKeyDownHandler(object sender, KeyEventArgs e)
{
if (string.IsNullOrEmpty(this.EmptyValue))
{
return;
}
string text = null;
// Handle the Backspace key
if (e.Key == Key.Back)
{
if (!this.TreatSelectedText(out text))
{
if (AssociatedObject.SelectionStart > 0)
text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1);
}
}
// Handle the Delete key
else if (e.Key == Key.Delete)
{
// If text was selected, delete it
if (!this.TreatSelectedText(out text) && this.AssociatedObject.Text.Length > AssociatedObject.SelectionStart)
{
// Otherwise delete next symbol
text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1);
}
}
if (text == string.Empty)
{
this.AssociatedObject.Text = this.EmptyValue;
if (e.Key == Key.Back)
AssociatedObject.SelectionStart++;
e.Handled = true;
}
}
private void PastingHandler(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(DataFormats.Text))
{
string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
if (!ValidateText(text))
e.CancelCommand();
}
else
e.CancelCommand();
}
/// <summary>
/// Validate certain text by our regular expression and text length conditions
/// </summary>
/// <param name="text"> Text for validation </param>
/// <returns> True - valid, False - invalid </returns>
private bool ValidateText(string text)
{
double number;
if (!Double.TryParse(text, out number))
{
return false;
}
if(number < Minimum)
{
return false;
}
if (number > Maximum)
{
return false;
}
int dotPointer = text.IndexOf('.');
// No point entered so the decimals must be ok
if(dotPointer == -1)
{
return true;
}
if (dotPointer > MaxInteger)
{
return false;
}
if(text.Substring(dotPointer +1).Length > MaxDecimals)
{
return false;
}
return true;
}
/// <summary>
/// Handle text selection
/// </summary>
/// <returns>true if the character was successfully removed; otherwise, false. </returns>
private bool TreatSelectedText(out string text)
{
text = null;
if (AssociatedObject.SelectionLength <= 0)
return false;
var length = this.AssociatedObject.Text.Length;
if (AssociatedObject.SelectionStart >= length)
return true;
if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length)
AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart;
text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
return true;
}
}
}
Usage:
<TextBox Text="{Binding ......>
<i:Interaction.Behaviors>
<ui:TextBoxDecimalRangeBehaviour MaxDecimals="2"
MaxInteger="1"
Minimum="{StaticResource Zero}"
Maximum="{StaticResource Ten}" />
<ui:SelectAllTextBoxBehavior/>
</i:Interaction.Behaviors>
</TextBox>
Upvotes: 0