Reputation: 1309
i wrote custom control base on TextBox which has also Minimum and Maximum inputs as follows:
public class NumericTextBox : TextBox
{
static NumericTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(typeof(NumericTextBox)));
}
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(int), typeof(NumericTextBox), new PropertyMetadata(default(int)));
public int Minimum
{
get { return (int)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(int), typeof(NumericTextBox), new PropertyMetadata(100));
public int Maximum
{
get { return (int)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public new static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(int), typeof(NumericTextBox),
new FrameworkPropertyMetadata(
default(int),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
null,
CoerceCurrentValue),
IsValid);
public new int Text
{
get { return (int)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private static object CoerceCurrentValue(DependencyObject d, object baseValue)
{
var numericTextBox = (NumericTextBox)d;
var intValue = (int)baseValue;
if (intValue < numericTextBox.Minimum) intValue = numericTextBox.Minimum;
if (intValue > numericTextBox.Maximum) intValue = numericTextBox.Maximum;
if ((int)baseValue != intValue)
numericTextBox.Text = intValue;
return intValue;
}
private static bool IsValid(object value)
{
if (value == null)
return false;
int intValue;
var result = Int32.TryParse(value.ToString(), out intValue);
return result;
}
}
and in my xaml i call it:
<controls:NumericTextBox
Grid.Row="0"
Grid.Column="1"
Margin="5"
VerticalAlignment="Center"
Text="{Binding Test, UpdateSourceTrigger=PropertyChanged}"
Minimum="0"
Maximum="100"
/>
it is bind to Test property in my view model (as int). everything works good until i type a character and i get binding error:
System.Windows.Data Error: 7 : ConvertBack cannot convert value '1a' (type 'String'). BindingExpression:Path=Text; DataItem='NumericTextBox' (Name=''); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') FormatException:'System.FormatException: Input string was not in a correct format. at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal) at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info) at System.String.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider) at MS.Internal.Data.SystemConvertConverter.ConvertBack(Object o, Type type, Object parameter, CultureInfo culture) at System.Windows.Data.BindingExpression.ConvertBackHelper(IValueConverter converter, Object value, Type sourceType, Object parameter, CultureInfo culture)'
it may be because the original Text property in TextBox is string... but i'm not sure. please assist on that.
Upvotes: 0
Views: 1389
Reputation: 69959
You are going about this in the wrong way. You are not restricting the input of the TextBox
to only numerical numbers. The Framework is trying to convert a string
into an int
to fit into your new int Text
property, but as the error says: Input string was not in a correct format., eg. not an int
. Try adding this into the constructor instead:
PreviewTextInput += new TextCompositionEventHandler((s, e) => e.Handled =
!e.Text.All(c => Char.IsNumber(c) && c != ' '));
PreviewKeyDown += new KeyEventHandler((s, e) => e.Handled = e.Key == Key.Space);
They simply work by setting e.Handled
to true
if non numerical values are input and this has the effect of ignoring the input on those occasions.
Also, you don't need to implement your own Text
property... that is just confusing matters. Just use the original one and parse the value into an int
wherever you need to, resting assured that it will be a number. I think that these two handlers should ensure that. Let me know if you have any problems.
Another idea is to simply create an AttachedProperty
using these handlers and then you can apply it to any TextBox
control. Of course, you'd then need to implement your Minimum
and Maximum
properties as AttachedProperties
too, but then you could do something like this:
<TextBox Text={Binding Test} Attached:TextBoxProperties.IsNumeric="True"
Attached:TextBoxProperties.Minimum="0" Attached:TextBoxProperties.Maximum="100" />
You can find out more from the Attached Properties Overview page on MSDN.
UPDATE >>>
If you don't want the user to be able to delete the last character from the TextBox
, then we can just change one of the handlers to disallow it:
PreviewKeyDown += PreviewKeyDown;
...
private void PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
e.Handled = e.Key == Key.Space ||
(textBox.Text.Length == 1 && (e.Key == Key.Delete || e.Key == Key.Back));
}
Upvotes: 1