I want create a custom control for Numeric Text box with dependency property in WPF , in my solution , I add one WPF application and custom control (WPF) ,then in public class , I create dependency property ....
Now I don't know how can i write my rule for text box and which event is true?
Another question : What is my rule for numeric text box , that this text box must be give number and . and Separating .this custom Text box is for accounting system.
public static readonly DependencyProperty NumberTextbox;
static Numeric()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Numeric), new FrameworkPropertyMetadata(typeof(Numeric)));
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata("Enter Your Text", OnKeyDown);
NumberTextbox =DependencyProperty.Register("Text", typeof(TextBox), typeof(FrameworkElement), metadata);
}
public string NumberTXT
{
get { return (string)GetValue(NumberTextbox); }
set { SetValue(NumberTextbox, value); }
}
I recommend to you add another Dependency Property in example code below I named it Value
Also format your number by comma or NumberFormatInfo.CurrentInfo.NumberDecimalSeparator
and control caret location changes by two property SelectionLength and SelectionStart.
Finally for more detail and complete code WPF Maskable Number Entry TextBox
region Value property
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(NumericTextBox), new PropertyMetadata(new Double(), OnValueChanged));
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
//var numericBoxControl = (NumericTextBox)sender;
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); Text = value.ToString("###,###,###"); }
}
endregion
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
var txt = e.Text.Replace(",", "");
e.Handled = !IsTextAllowed(txt);
if (IsTextAllowed(txt))
{
if (Text.Length == 3)
{
Text = Text.Insert(1,",");
SelectionLength = 1;
SelectionStart += Text.Length;
}
}
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Back)
{
if (Text.Length == 5)
{
Text = Text.Replace(",", "");
SelectionLength = 1;
SelectionStart += Text.Length;
}
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
var txt = Text.Replace(",", "");
SetValue(ValueProperty, txt.Length==0?0:double.Parse(txt));
base.OnTextChanged(e);
}
private static bool IsTextAllowed(string text)
{
try
{
double.Parse(text);
return true;
}
catch (FormatException)
{
return false;
}
}
I don't understand your question exactly and why you need dependency proerties to make a numeric text box custom control. What you can do is to inherit from textbox and handle the PreviewTextInput, like it is solved in this question by Ray:
then you get:
public class NumericTextBox : TextBox
{
public NumericTextBox()
{
PreviewTextInput += NumericTextBox_PreviewTextInput;
}
void NumericTextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !isTextAllowed(e.Text);
}
private static bool isTextAllowed(string text)
{
var regex = new Regex("[^0-9]+");
return !regex.IsMatch(text);
}
}
And you can use it like that:
<myNameSpace:NumericTextBox />
And now you can add any other validation you want.
I would also implement a solution for the pasting issue, something like (see also in the link):
private void textBoxPasting(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(String)))
{
var text = (String)e.DataObject.GetData(typeof(String));
if (!isTextAllowed(text))
{
e.CancelCommand();
}
}
else
{
e.CancelCommand();
}
}
Good job, but let me do the following task with a UserControl in C #:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace NumericBox
{
public partial class NumericBox : TextBox
{
public NumericBox
{
this.TextAlign = HorizontalAlignment.Right;
this.Text = "0,00";
this.KeyPress += NumericBox_KeyPress;
}
public double NumericResult
{
get{
double d = Convert.ToDouble(this.Text.Replace('.', ','));
return d;
}
}
private void NumericBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar) && (e.KeyChar != '.'))
e.Handled = true;
if ((e.KeyChar == '.' && (sender as TextBox).Text.IndexOf('.') > -1))
e.Handled = true;
if (e.KeyChar == 13)
{
e.Handled = true;
SendKeys.Send("{TAB}");
}
}
}
}
Related
I'm curious about a good way to notify a user that the value he was trying to enter into a Textbox is not valid. I'd prefer to have a red-flash animation over the textbox to inform the user that the value has been ignored because it's not valid.
However I'm using a Behaviour for my TextBox which gives me the ability to use a regular expression to determine which characters/words are valid and which not.
How or where would I have to modify my code to get this additional feature? I'm not quite sure if this is possible with my Behaviour because invalid input is cancelled before it's entering the TextBox.
Usually I'd check the input in my ViewModel and if it's invalid I'd inform my controller to show an error but because my Behaviour is dealing with this problem I'm not quite sure where to start.
This is my Behaviour:
public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty RegularExpressionProperty =
DependencyProperty.Register("RegularExpression", typeof (string),
typeof (AllowableCharactersTextBoxBehavior),
new FrameworkPropertyMetadata("*"));
public string RegularExpression
{
get { return (string) base.GetValue(RegularExpressionProperty); }
set { base.SetValue(RegularExpressionProperty, value); }
}
public static readonly DependencyProperty MaxLengthProperty =
DependencyProperty.Register("MaxLength", typeof (int), typeof (AllowableCharactersTextBoxBehavior),
new FrameworkPropertyMetadata(int.MinValue));
public int MaxLength
{
get { return (int) base.GetValue(MaxLengthProperty); }
set { base.SetValue(MaxLengthProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTextInput += OnPreviewTextInput;
DataObject.AddPastingHandler(AssociatedObject, OnPaste);
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(DataFormats.Text))
{
string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
if (!IsValid(text, true))
{
e.CancelCommand();
}
}
else
{
e.CancelCommand();
}
}
private void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !IsValid(e.Text, false);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
DataObject.RemovePastingHandler(AssociatedObject, OnPaste);
}
private bool IsValid(string newText, bool paste)
{
return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(newText, RegularExpression);
}
private bool ExceedsMaxLength(string newText, bool paste)
{
if (MaxLength == 0) return false;
return LengthOfModifiedText(newText, paste) > MaxLength;
}
private int LengthOfModifiedText(string newText, bool paste)
{
var countOfSelectedChars = this.AssociatedObject.SelectedText.Length;
var caretIndex = this.AssociatedObject.CaretIndex;
string text = this.AssociatedObject.Text;
if (countOfSelectedChars > 0 || paste)
{
text = text.Remove(caretIndex, countOfSelectedChars);
return text.Length + newText.Length;
}
else
{
var insert = Keyboard.IsKeyToggled(Key.Insert);
return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
}
}
}
I think I would have to modify the code in the OnPreviewTextInput Method to signalize that the input was wrong. But how do I add a flash animation or something else to it?
For styling your Validation.ErrorTemplate look at this SO answer for some ideas.
For setting your control in an invalid state you can use (assuming you have bound the TextProperty to your viewmodel)
private void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !IsValid(e.Text, false);
if(e.Handled)
{
var be = BindingOperations.GetBindingExpression(AssociatedObject, TextBox.TextProperty);
Validation.MarkInvalid(be, new ValidationError(new DummyValidationRule(), be.ParentBinding));
}
}
private class DummyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
return new ValidationResult(false, "ErrorMessage");
}
}
Considered to use the build-in mechanisms of wpf?
Here is a blog post from Magnus Montin with a lot of information about validation in wpf.
I have a requirement that I want to make a datagridcolumn which only accepts numeric values(integer) ,when the user enter something other than numbers handle the textbox .
I tried a lot of webpages ,Iam tired of these ,I greately appreciate anybody have the helping mind.
Based on #nit suggestion, you can create your own class derived from DataGridTextColumn like this:
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(System.Windows.FrameworkElement editingElement, System.Windows.RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
try
{
Convert.ToInt32(e.Text);
}
catch
{
// Show some kind of error message if you want
// Set handled to true
e.Handled = true;
}
}
}
In the PrepareCellForEdit method you register the OnPreviewTextInput method to the editing TextBox PreviewTextInput event, where you validate for numeric values.
In xaml, you simply use it:
<DataGrid ItemsSource="{Binding SomeCollection}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding NonNumericProperty}"/>
<local:DataGridNumericColumn Binding="{Binding NumericProperty}"/>
</DataGrid.Columns>
</DataGrid>
Hope this helps
Use TryParse instead, this helps to restrict input values to integer numbers only.
/// <summary>
/// This class help to create data grid cell which only support interger numbers.
/// </summary>
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
if (edit != null) edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
private void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
int value;
if (!int.TryParse(e.Text, out value))
e.Handled = true;
}
}
If you dont want to show any validation errors and just want to block any non-numeral value then you can create the DataGridTemplateColumn and in CellEditingTemplate use the TextBox.
<DataGridTemplateColumn Width="100*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=NumericProperty}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox PreviewTextInput="TextBox_PreviewTextInput" Text="{Binding Path=NumericProperty}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
and in PreviewTextInput of the TextBox set e.Handled = true if value is other than integer:
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
try
{
Convert.ToInt32(e.Text);
}
catch
{
e.Handled = true;
}
}
I got here looking for a solution to the same problem: constraining the input into cells on a DataGrid to be numeric. But the accepted answer did not work for me. The following did:
For the DataGrid add an event handler for PreparingForCellEdit.
In that event handler, cast the EditingElement to a TextBox and add an event handler for PreviewTextInput to the TextBox.
In the PreviewTextInput event handler set e.Handled to true, if the input should not be allowed.
The above steps work if the user clicks the cell to edit. However, if the cell is not in edit mode, the PreparingForCellEdit event will not be called. To perform validation in that case:
Add an event handler to the DataGrid for PreviewTextInput.
In that event handler, safely cast e.OriginalSource to a DataGridCell (exiting, if it is not a DataGridCell), check the DataGridCell's IsEditing property, and if the cell is not editing set e.Handled to true.
The effect of the above is that the user will have to click into the cell in order to edit its contents and, as such, the PreparingForCellEdit / PreviewTextInput combination above will be invoked for all changes to the cell's contents.
Just to extend #Omribitan's answer, Here is the solution with a data Paste guard added:
public class NumericTextColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var edit = editingElement as TextBox;
edit.PreviewTextInput += Edit_PreviewTextInput;
DataObject.AddPastingHandler(edit, OnPaste);
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
var data = e.SourceDataObject.GetData(DataFormats.Text);
if (!IsDataValid(data)) e.CancelCommand();
}
private void Edit_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = !IsDataValid(e.Text);
}
bool IsDataValid(object data)
{
try
{
Convert.ToInt32(data);
return true;
}
catch
{
return false;
}
}
}
For whatever it's worth, here's how I solved it. This solution allows you to specify a variety of options when validating input, allows string formatting to be used (e.g. '$15.00' in the data grid) and more.
The null value and string formatting provided by the Binding class itself do not suffice as neither act correctly when the cell is editable so this class covers it. What this does is it uses another class that I've been using for a long time already: TextBoxInputBehavior, it has been an invaluable asset for me and it originally came from WPF – TextBox Input Behavior blog post albeit the version here seems much older (but well tested). So what I did I just transferred this existing functionality I already had on my TextBoxes to the my custom column and thus I have the same behaviour in both. Isn't that neat?
Here's the code of the custom column:
public class DataGridNumberColumn : DataGridTextColumn
{
private TextBoxInputBehavior _behavior;
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem);
// A clever workaround the StringFormat issue with the Binding set to the 'Binding' property. If you use StringFormat it
// will only work in edit mode if you changed the value, otherwise it will retain formatting when you enter editing.
if (!string.IsNullOrEmpty(StringFormat))
{
BindingOperations.ClearBinding(element, TextBlock.TextProperty);
BindingOperations.SetBinding(element, FrameworkElement.TagProperty, Binding);
BindingOperations.SetBinding(element,
TextBlock.TextProperty,
new Binding
{
Source = element,
Path = new PropertyPath("Tag"),
StringFormat = StringFormat
});
}
return element;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
if (!(editingElement is TextBox textBox))
return null;
var originalText = textBox.Text;
_behavior = new TextBoxInputBehavior
{
IsNumeric = true,
EmptyValue = EmptyValue,
IsInteger = IsInteger
};
_behavior.Attach(textBox);
textBox.Focus();
if (editingEventArgs is TextCompositionEventArgs compositionArgs) // User has activated editing by already typing something
{
if (compositionArgs.Text == "\b") // Backspace, it should 'clear' the cell
{
textBox.Text = EmptyValue;
textBox.SelectAll();
return originalText;
}
if (_behavior.ValidateText(compositionArgs.Text))
{
textBox.Text = compositionArgs.Text;
textBox.Select(textBox.Text.Length, 0);
return originalText;
}
}
if (!(editingEventArgs is MouseButtonEventArgs) || !PlaceCaretOnTextBox(textBox, Mouse.GetPosition(textBox)))
textBox.SelectAll();
return originalText;
}
private static bool PlaceCaretOnTextBox(TextBox textBox, Point position)
{
int characterIndexFromPoint = textBox.GetCharacterIndexFromPoint(position, false);
if (characterIndexFromPoint < 0)
return false;
textBox.Select(characterIndexFromPoint, 0);
return true;
}
protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
{
UnwireTextBox();
base.CancelCellEdit(editingElement, uneditedValue);
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
UnwireTextBox();
return base.CommitCellEdit(editingElement);
}
private void UnwireTextBox() => _behavior.Detach();
public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register(
nameof(EmptyValue),
typeof(string),
typeof(DataGridNumberColumn));
public string EmptyValue
{
get => (string)GetValue(EmptyValueProperty);
set => SetValue(EmptyValueProperty, value);
}
public static readonly DependencyProperty IsIntegerProperty = DependencyProperty.Register(
nameof(IsInteger),
typeof(bool),
typeof(DataGridNumberColumn));
public bool IsInteger
{
get => (bool)GetValue(IsIntegerProperty);
set => SetValue(IsIntegerProperty, value);
}
public static readonly DependencyProperty StringFormatProperty = DependencyProperty.Register(
nameof(StringFormat),
typeof(string),
typeof(DataGridNumberColumn));
public string StringFormat
{
get => (string) GetValue(StringFormatProperty);
set => SetValue(StringFormatProperty, value);
}
}
What I did is I peeked into the source code of DataGridTextColumn and handled the TextBox creation in almost the same way plus I attached the custom behaviour to the TextBox.
Here's the code of the behavior I attached (this is a behavior you can use on any TextBox):
public class TextBoxInputBehavior : Behavior<TextBox>
{
#region DependencyProperties
public static readonly DependencyProperty RegularExpressionProperty = DependencyProperty.Register(
nameof(RegularExpression),
typeof(string),
typeof(TextBoxInputBehavior),
new FrameworkPropertyMetadata(".*"));
public string RegularExpression
{
get
{
if (IsInteger)
return #"^[0-9\-]+$";
if (IsNumeric)
return #"^[0-9.\-]+$";
return (string)GetValue(RegularExpressionProperty);
}
set { SetValue(RegularExpressionProperty, value); }
}
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register(
nameof(MaxLength),
typeof(int),
typeof(TextBoxInputBehavior),
new FrameworkPropertyMetadata(int.MinValue));
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register(
nameof(EmptyValue),
typeof(string),
typeof(TextBoxInputBehavior));
public string EmptyValue
{
get { return (string)GetValue(EmptyValueProperty); }
set { SetValue(EmptyValueProperty, value); }
}
public static readonly DependencyProperty IsNumericProperty = DependencyProperty.Register(
nameof(IsNumeric),
typeof(bool),
typeof(TextBoxInputBehavior));
public bool IsNumeric
{
get { return (bool)GetValue(IsNumericProperty); }
set { SetValue(IsNumericProperty, value); }
}
public static readonly DependencyProperty IsIntegerProperty = DependencyProperty.Register(
nameof(IsInteger),
typeof(bool),
typeof(TextBoxInputBehavior));
public bool IsInteger
{
get { return (bool)GetValue(IsIntegerProperty); }
set
{
if (value)
SetValue(IsNumericProperty, true);
SetValue(IsIntegerProperty, value);
}
}
public static readonly DependencyProperty AllowSpaceProperty = DependencyProperty.Register(
nameof(AllowSpace),
typeof (bool),
typeof (TextBoxInputBehavior));
public bool AllowSpace
{
get { return (bool) GetValue(AllowSpaceProperty); }
set { SetValue(AllowSpaceProperty, value); }
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTextInput += PreviewTextInputHandler;
AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler;
DataObject.AddPastingHandler(AssociatedObject, PastingHandler);
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject == null)
return;
AssociatedObject.PreviewTextInput -= PreviewTextInputHandler;
AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler;
DataObject.RemovePastingHandler(AssociatedObject, PastingHandler);
}
private void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
string text;
if (AssociatedObject.Text.Length < AssociatedObject.CaretIndex)
text = AssociatedObject.Text;
else
text = TreatSelectedText(out var remainingTextAfterRemoveSelection)
? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text)
: AssociatedObject.Text.Insert(AssociatedObject.CaretIndex, e.Text);
e.Handled = !ValidateText(text);
}
private void PreviewKeyDownHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
e.Handled = !AllowSpace;
if (string.IsNullOrEmpty(EmptyValue))
return;
string text = null;
// Handle the Backspace key
if (e.Key == Key.Back)
{
if (!TreatSelectedText(out text))
{
if (AssociatedObject.SelectionStart > 0)
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1);
}
}
// Handle the Delete key
else if (e.Key == Key.Delete)
{
// If text was selected, delete it
if (!TreatSelectedText(out text) && AssociatedObject.Text.Length > AssociatedObject.SelectionStart)
{
// Otherwise delete next symbol
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1);
}
}
if (text == string.Empty)
{
AssociatedObject.Text = EmptyValue;
if (e.Key == Key.Back)
AssociatedObject.SelectionStart++;
e.Handled = true;
}
}
private void PastingHandler(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(DataFormats.Text))
{
var text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
if (!ValidateText(text))
e.CancelCommand();
}
else
e.CancelCommand();
}
public bool ValidateText(string text)
{
return new Regex(RegularExpression, RegexOptions.IgnoreCase).IsMatch(text) && (MaxLength == int.MinValue || text.Length <= MaxLength);
}
/// <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 = AssociatedObject.Text.Length;
if (AssociatedObject.SelectionStart >= length)
return true;
if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length)
AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart;
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
return true;
}
}
All the good credit for above Behaviour class goes to blindmeis, I merely tweaked it over time. After checking his Blog I see he has a newer version of it so you may check it out. I was very happy to find out I could use his behaviour on DataGrid as well!
This solution worked really well, you can edit the cell properly via mouse/keyboard, paste the contents properly, use any binding source update triggers, use any string formatting etc. - it just works.
Here's an example of how to use it:
<local:DataGridNumberColumn Header="Nullable Int Currency" IsInteger="True" Binding="{Binding IntegerNullable, TargetNullValue=''}" StringFormat="{}{0:C}" />
Hope this helps someone.
I went on from Omris approach
however i wanted to be able to delete the cell value after it has input in case they wanted to clear it
The way i did this was overriding the CommitCellEdit method and making the string null instead of blank. Im also using decimal? in my case
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(System.Windows.FrameworkElement editingElement, System.Windows.RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
protected override bool CommitCellEdit(System.Windows.FrameworkElement editingElement)
{
TextBox tb = editingElement as TextBox;
if (string.IsNullOrEmpty(tb.Text))
tb.Text = null;
return base.CommitCellEdit(editingElement);
}
void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
try
{
Convert.ToDecimal(e.Text);
}
catch
{
// Show some kind of error message if you want
// Set handled to true
e.Handled = true;
}
}
}
I have an input box that should allow numbers/currency only. For this purpose I use the InputScope "CurrencyAmount".
When I run the code a numeric keyboard will pop up but the user is allowed to enter many decimal point instead of just one.
Example:
Inputs like "12.50" should be allowed in the textbox, but the user is able to enter value like "12....50", "..12.5....0" etc. instead.
How can I restrict the allowed textbox values to match my criterium?
I would go with a behavior and a regex. Then you can easily reuse your code for other textboxes as well.
public class RegexValidationBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty RegexStringProperty =
DependencyProperty.Register("RegexString", typeof(string), typeof(RegexValidationBehavior), new PropertyMetadata(string.Empty));
public string RegexString
{
get { return GetValue(RegexStringProperty) as string; }
set { SetValue(RegexStringProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (AssociatedObject != null)
{
AssociatedObject.TextChanged += OnTextChanged;
}
Validate();
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.TextChanged -= OnTextChanged;
}
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
Validate();
}
private void Validate()
{
var value = AssociatedObject.Text;
if (value.IsNotEmpty() && RegexString.IsNotEmpty())
{
MatchAgainstRegex(value);
}
}
private void MatchAgainstRegex(string value)
{
var match = Regex.Match(value, RegexString);
if (!match.Success)
{
AssociatedObject.Text = value.Remove(value.Length - 1);
AssociatedObject.Select(AssociatedObject.Text.Length, 0);
}
}
}
Then in your XAML write something like.
<TextBox InputScope="Number" Text="{Binding Amount, Mode=TwoWay}">
<i:Interaction.Behaviors>
<Control:RegexValidationBehavior RegexString="{Binding OnlyTwoDecimalsRegex}"/>
</i:Interaction.Behaviors>
</TextBox>
In your ViewModel you specify a Regex, for example
public string OnlyTwoDecimalsRegex { get { return #"^([0-9]+)?([,|\.])?([0-9]{1,2})?$"; } }
you can try subscribing to TextChanged event for textbox and run below validation - works well for other locales too apart from en-US.
string decimalsep = CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator;
int decimalSepCount = text1.Text.Count(f => f == decimalsep[0]);
if (decimalSepCount > 1)
{
MessageBox.Show("Invalid input");
}
I would append a key down event handler to your textbox and validate if your input matches your predicate.
Pseudocode:
//...
//register event handler
yourTextBox.KeyDown += new KeyEventHandler(yourTextBox_KeyDown);
//...
//the keydown event
public void yourTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (System.Text.RegularExpressions.Regex.IsMatch(yourTextBox.Text,"<enter a regular expression here>"))
e.Handled = true;
else e.Handled = false;
}
This question already has answers here:
How do I make a textbox that only accepts numbers?
(41 answers)
Closed 9 years ago.
I am creating an old-school dialog in c# using a System.Windows.Controls.TextBox .
Is there an easy way of limiting text input in this box to numeric only?
Thanks!
Just implement the onkeyup event handler and if the key pressed is not a Character.IsDigit then clear it.
http://msdn.microsoft.com/en-us/library/system.windows.controls.textbox.onkeyup(VS.95).aspx
You could consider using a MaskedTextBox, setting the Mask property accordingly.
This is an extract from my answer to an earlier question.
Add this class to your project
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class TextBoxFilter
{
[Flags()]
public enum Filters
{
None = 0,
Text = 1,
Numbers = 2,
AlphaNumeric = Filters.Text | Filters.Numbers,
Currency = 4,
All = Filters.Text | Filters.Numbers | Filters.Currency
}
Dictionary<TextBox, Filters> _keyFilter;
Dictionary<TextBox, string> _allowedKeys;
Dictionary<TextBox, string> _invalidKeys;
Dictionary<TextBox, Windows.Forms.KeyEventArgs> keyEventArgs;
private static string DecimalMark = Application.CurrentCulture.NumberFormat.NumberDecimalSeparator;
private static string NegativeMark = Application.CurrentCulture.NumberFormat.NegativeSign;
private static string CurrencySymb = Application.CurrentCulture.NumberFormat.CurrencySymbol;
private static string CurrencyDecimal = Application.CurrentCulture.NumberFormat.CurrencyDecimalSeparator;
public TextBoxFilter()
{
_keyFilter = new Dictionary<TextBox, Filters>();
_allowedKeys = new Dictionary<TextBox, string>();
_invalidKeys = new Dictionary<TextBox, string>();
keyEventArgs = new Dictionary<TextBox, KeyEventArgs>();
}
//set & remove filter
public void SetTextBoxFilter(TextBox textBox, Filters filter)
{
SetTextBoxFilter(textBox, filter, AllowedKeys(textBox), InvalidKeys(textBox));
}
public void SetTextBoxFilter(TextBox textBox, string allowedKeys)
{
SetTextBoxFilter(textBox, Strings.Filter(textBox), allowedKeys, InvalidKeys(textBox));
}
public void SetTextBoxFilter(TextBox textBox, string allowedKeys, string invalidKeys)
{
SetTextBoxFilter(textBox, Strings.Filter(textBox), allowedKeys, invalidKeys);
}
public void SetTextBoxFilter(TextBox textBox, Filters filter, string allowedKeys, string invalidKeys)
{
if (!_keyFilter.ContainsKey(textBox)) {
//add the textbox and its filter if it does not exist in
//the collection of registered textboxes
_keyFilter.Add(textBox, filter);
_allowedKeys.Add(textBox, allowedKeys);
_invalidKeys.Add(textBox, invalidKeys);
keyEventArgs.Add(textBox, new System.Windows.Forms.KeyEventArgs(Keys.None));
//add the event handlers
textBox.KeyDown += KeyDownUp;
textBox.KeyUp += KeyDownUp;
textBox.KeyPress += KeyPress;
textBox.Validating += Validating;
textBox.Disposed += Disposed;
} else {
//change the filter of the textbox if it exists in
//the collection of registered textboxes
_keyFilter(textBox) = filter;
_allowedKeys(textBox) = allowedKeys;
_invalidKeys(textBox) = invalidKeys;
}
}
public void RemoveTextBoxFilter(TextBox textBox)
{
if (_keyFilter.ContainsKey(textBox)) {
_keyFilter.Remove(textBox);
_allowedKeys.Remove(textBox);
_invalidKeys.Remove(textBox);
keyEventArgs.Remove(textBox);
textBox.KeyDown -= KeyDownUp;
textBox.KeyUp -= KeyDownUp;
textBox.KeyPress -= KeyPress;
textBox.Validating -= Validating;
textBox.Disposed -= Disposed;
}
}
public bool ContainsTextBox(TextBox textBox)
{
return _keyFilter.ContainsKey(textBox);
}
//properties
public Filters Filter {
get {
if (ContainsTextBox(textBox)) {
return _keyFilter.Item[textBox];
} else {
return Filters.None;
}
}
set { SetTextBoxFilter(textBox, value); }
}
public string AllowedKeys {
get {
if (ContainsTextBox(textBox)) {
return _allowedKeys(textBox);
} else {
return "";
}
}
set { SetTextBoxFilter(textBox, this.Filter(textBox), value, this.InvalidKeys(textBox)); }
}
public string InvalidKeys {
get {
if (ContainsTextBox(textBox)) {
return _invalidKeys(textBox);
} else {
return "";
}
}
set { SetTextBoxFilter(textBox, this.Filter(textBox), this.AllowedKeys(textBox), value); }
}
//event handlers
private void Disposed(object sender, System.EventArgs e)
{
RemoveTextBoxFilter((TextBox)sender);
}
private void KeyDownUp(object sender, System.Windows.Forms.KeyEventArgs e)
{
//assign the modifiers
keyEventArgs((TextBox)sender) = e;
}
private void KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
//ensure key pressed is in the allowed keys
object txt = (TextBox)sender;
object c = e.KeyChar;
bool allowKey = IsValidChar(txt, c, txt.SelectionStart);
//check for backspace & Ctrl combinations if the allowKey is still false
if (allowKey == false) {
if (keyEventArgs(txt).Control) {
//control modifier goes with A, X, C, V and Z for
//Select All, Cut, Copy, Paste and Undo respectively
object key = keyEventArgs(txt).KeyCode;
allowKey = (key == Keys.A || key == Keys.X || key == Keys.C || key == Keys.V || key == Keys.Z);
} else if (keyEventArgs(txt).KeyCode == Keys.Back) {
//allow the backspace key
allowKey = true;
}
}
//disable the key if it was not valid
if (!allowKey) {
e.Handled = true;
Interaction.Beep();
}
}
private void Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
object box = (TextBox)sender;
object boxFlags = _keyFilter(box);
//skip validation if the textbox allows all entries or there is no text
if (boxFlags == Filters.All | string.IsNullOrEmpty(box.Text))
return;
//otherwise check the characters entered
object txtChars = box.Text.ToCharArray;
bool isValidEntry = false;
//check each caracter for an invalid entry
for (i = 0; i <= txtChars.Length - 1; i++) {
object c = txtChars(i);
isValidEntry = IsValidChar(box, txtChars(i), i);
if (!isValidEntry) {
box.Select(i, 1);
break; // TODO: might not be correct. Was : Exit For
}
}
if (!isValidEntry)
e.Cancel = true;
if (!isValidEntry) {
Interaction.MsgBox("The text entered is invalid for the format " + boxFlags.ToString + "." + !string.IsNullOrEmpty(_allowedKeys(box)) ? Constants.vbCrLf + "Additional Allowed Keys: " + _allowedKeys(box) : "" + !string.IsNullOrEmpty(_invalidKeys(box)) ? Constants.vbCrLf + "Additional Invalid Keys: " + _invalidKeys(box) : "", MsgBoxStyle.Critical, "Invalid Entry");
}
}
private bool IsValidChar(TextBox textBox, char c, int charIndex)
{
//ensure key pressed is in the allowed keys
object pF = _keyFilter(textBox);
object aK = _allowedKeys(textBox);
object iK = _invalidKeys(textBox);
bool shouldAllow = false;
//if filter is set to all, return true unconditionally
if (pF == Filters.All)
return true;
//check preset filters
//check for text
if (EnumHasFlag(pF, Filters.Text)) {
if (!char.IsDigit(c)) {
shouldAllow = true;
} else {
//if the character is a digit, check for the number flag (AlphaNumerics)
if (EnumHasFlag(pF, Filters.Numbers)) {
shouldAllow = true;
}
}
}
//check for nubers
if (shouldAllow == false && EnumHasFlag(pF, Filters.Numbers)) {
if (char.IsDigit(c)) {
shouldAllow = true;
} else if (DecimalMark.Contains(c)) {
//allow the decimal if there is no decimal in the textbox's
//text or the selected text contains the mark
if (!textBox.Text.Substring(0, charIndex).Contains(c) || textBox.SelectedText.Contains(c)) {
shouldAllow = true;
}
} else if (NegativeMark.Contains(c) && (charIndex <= NegativeMark.IndexOf(c))) {
//allow the negative mark if we are at the start of the
//textbox
shouldAllow = true;
}
}
//check for currency
if (shouldAllow == false && EnumHasFlag(pF, Filters.Currency)) {
if (char.IsDigit(c)) {
shouldAllow = true;
} else if (CurrencyDecimal.Contains(c)) {
//allow the currency decimal mark if it does not exist in the
//textbox's text or the selected text contains the mark
if (!textBox.Text.Substring(0, charIndex).Contains(c) || textBox.SelectedText.Contains(c)) {
shouldAllow = true;
}
} else if (CurrencySymb.Contains(c) && (charIndex <= CurrencySymb.IndexOf(c))) {
//allow the currency symbol if we are in a valid position
shouldAllow = true;
}
}
//now check for extra allowed keys
if (!shouldAllow) {
shouldAllow = aK.Contains(c);
}
//and then check for extra invalid keys
if (shouldAllow && iK.Contains(c)) {
shouldAllow = false;
}
return shouldAllow;
}
[System.Diagnostics.DebuggerStepThrough()]
private bool EnumHasFlag(Enum value, Enum flag)
{
return (Convert.ToInt64(value) & Convert.ToInt64(flag)) == Convert.ToInt64(flag);
}
}
and then use it in your form as follows
public class Form1
{
TextBoxFilter filter = new TextBoxFilter();
private void Form1_Load(object sender, System.EventArgs e)
{
filter.SetTextBoxFilter(TextBox1, TextBoxFilter.Filters.Numbers);
}
public Form1()
{
Load += Form1_Load;
}
}
Also consider whether your design needs to restrict user input when typed/pasted or if it's OK to simply convert the user input to a number when he leaves the textbox.
Often the restrictive approach is harder to do (dealing with commas, periods, currency symbols, spaces, etc... can be a huge pain)
This method then becomes as simple as:
private void textBox1_Leave(object sender, EventArgs e)
{
textBox1.Text = VerifyNumeric(textBox1.Text);
}
private string VerifyNumeric(string text)
{
double value = 0;
double.TryParse(text, out value);
return value.ToString(); // could format here too.
}
if (!(char.IsDigit(e.KeyChar)))
{
e.Handled = true;
}
The above code is not working properly
Below is the image error :
The problem space is "Clipboard"
If this is for WinForms, my suggestion would be to use a MaskedTextBox instead. This is a purpose-built control for allowing only certain kinds of user-input.
You can set the mask through the designer or in code.
For example, for a 5-digit numeric:
maskedTextBox1.Mask = "00000";
maskedTextBox1.ValidatingType = typeof(int);
Yes, this is the typical nemesis for keyboard filtering. The TextBox control doesn't have any built-in events to intercept a paste from the clipboard. You'll have to detect the Ctrl+V keypress yourself and screen Clipboard.GetText().
The logic is tricky to get right. Here's a class that can make all this a little easier. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto a form. Double click it and write the ValidateChar event handler. Like this one, only allowing entering digits:
private void validatingTextBox1_ValidateChar(object sender, ValidateCharArgs e) {
if (!"0123456789".Contains(e.KeyChar)) e.Cancel = true;
}
The code:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Text;
[DefaultEvent("ValidateChar")]
class ValidatingTextBox : TextBox {
public event EventHandler<ValidateCharArgs> ValidateChar;
protected virtual void OnValidateChar(ValidateCharArgs e) {
var handler = ValidateChar;
if (handler != null) handler(this, e);
}
protected override void OnKeyPress(KeyPressEventArgs e) {
if (e.KeyChar >= ' ') { // Allow the control keys to work as normal
var args = new ValidateCharArgs(e.KeyChar);
OnValidateChar(args);
if (args.Cancel) {
e.Handled = true;
return;
}
}
base.OnKeyPress(e);
}
private void HandlePaste() {
if (!Clipboard.ContainsText()) return;
string text = Clipboard.GetText();
var toPaste = new StringBuilder(text.Length);
foreach (char ch in text.ToCharArray()) {
var args = new ValidateCharArgs(ch);
OnValidateChar(args);
if (!args.Cancel) toPaste.Append(ch);
}
if (toPaste.Length != 0) {
Clipboard.SetText(toPaste.ToString());
this.Paste();
}
}
bool pasting;
protected override void WndProc(ref Message m) {
if (m.Msg == 0x302 && !pasting) {
pasting = true;
HandlePaste();
pasting = false;
}
else base.WndProc(ref m);
}
}
class ValidateCharArgs : EventArgs {
public ValidateCharArgs(char ch) { Cancel = false; KeyChar = ch; }
public bool Cancel { get; set; }
public char KeyChar { get; set; }
}
Handle TextChanged event or use a MaskedTextBox.
if (textBox1.Text.Count(a => !char.IsDigit(a)) > 0)
{
textBox1.Text = new string(textBox1.Text.Where(a => char.IsDigit(a)).ToArray());
}
I answered a similar question on StackOverflow once.
Here's the link to the question: Best way to limit textbox decimal input in c#
Essentially, you'll have to put my class in your code and apply it to all textboxes you want to restrict data entered.
The TextBoxFilter class I wrote allows you to limit entry to Alphabet, Numerics, AlphaNumerics, Currency and UserSpecified input.
control.TextChanged += (s, a) => {
string value = string.Empty;
foreach (char ch in control.Text.ToCharArray())
{
if (char.IsDigit(ch))
{
value += ch.ToString();
}
}
control.Text = value;
};