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 tri
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
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:
DataGrid
add an event handler for PreparingForCellEdit
.EditingElement
to a TextBox
and add an event handler for PreviewTextInput
to the TextBox
.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:
DataGrid
for PreviewTextInput
.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.
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;
}
}
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.
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;
}
}