Numeric Data Entry in WPF - c#

How are you handling the entry of numeric values in WPF applications?
Without a NumericUpDown control, I've been using a TextBox and handling its PreviewKeyDown event with the code below, but it's pretty ugly.
Has anyone found a more graceful way to get numeric data from the user without relying on a third-party control?
private void NumericEditPreviewKeyDown(object sender, KeyEventArgs e)
{
bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9) || e.Key == Key.Decimal;
bool isNumeric = (e.Key >= Key.D0 && e.Key <= Key.D9) || e.Key == Key.OemPeriod;
if ((isNumeric || isNumPadNumeric) && Keyboard.Modifiers != ModifierKeys.None)
{
e.Handled = true;
return;
}
bool isControl = ((Keyboard.Modifiers != ModifierKeys.None && Keyboard.Modifiers != ModifierKeys.Shift)
|| e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Insert
|| e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up
|| e.Key == Key.Tab
|| e.Key == Key.PageDown || e.Key == Key.PageUp
|| e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Escape
|| e.Key == Key.Home || e.Key == Key.End);
e.Handled = !isControl && !isNumeric && !isNumPadNumeric;
}

How about:
protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !AreAllValidNumericChars(e.Text);
base.OnPreviewTextInput(e);
}
private bool AreAllValidNumericChars(string str)
{
foreach(char c in str)
{
if(!Char.IsNumber(c)) return false;
}
return true;
}

This is how I do it. It uses a regular expression to check if the text that will be in the box is numeric or not.
Regex NumEx = new Regex(#"^-?\d*\.?\d*$");
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (sender is TextBox)
{
string text = (sender as TextBox).Text + e.Text;
e.Handled = !NumEx.IsMatch(text);
}
else
throw new NotImplementedException("TextBox_PreviewTextInput Can only Handle TextBoxes");
}
There is now a much better way to do this in WPF and Silverlight. If your control is bound to a property, all you have to do is change your binding statement a bit. Use the following for your binding:
<TextBox Text="{Binding Number, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"/>
Note that you can use this on custom properties too, all you have to do is throw an exception if the value in the box is invalid and the control will get highlighted with a red border. If you click on the upper right of the red border then the exception message will pop up.

I've been using an attached property to allow the user to use the up and down keys to change the values in the text box. To use it, you just use
<TextBox local:TextBoxNumbers.SingleDelta="1">100</TextBox>
This doesn't actually address the validation issues that are referred to in this question, but it addresses what I do about not having a numeric up/down control. Using it for a little bit, I think I might actually like it better than the old numeric up/down control.
The code isn't perfect, but it handles the cases I needed it to handle:
Up arrow, Down arrow
Shift + Up arrow, Shift + Down arrow
Page Up, Page Down
Binding Converter on the text property
Code behind
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace Helpers
{
public class TextBoxNumbers
{
public static Decimal GetSingleDelta(DependencyObject obj)
{
return (Decimal)obj.GetValue(SingleDeltaProperty);
}
public static void SetSingleDelta(DependencyObject obj, Decimal value)
{
obj.SetValue(SingleDeltaProperty, value);
}
// Using a DependencyProperty as the backing store for SingleValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SingleDeltaProperty =
DependencyProperty.RegisterAttached("SingleDelta", typeof(Decimal), typeof(TextBoxNumbers), new UIPropertyMetadata(0.0m, new PropertyChangedCallback(f)));
public static void f(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
TextBox t = o as TextBox;
if (t == null)
return;
t.PreviewKeyDown += new System.Windows.Input.KeyEventHandler(t_PreviewKeyDown);
}
private static Decimal GetSingleValue(DependencyObject obj)
{
return GetSingleDelta(obj);
}
private static Decimal GetDoubleValue(DependencyObject obj)
{
return GetSingleValue(obj) * 10;
}
private static Decimal GetTripleValue(DependencyObject obj)
{
return GetSingleValue(obj) * 100;
}
static void t_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
TextBox t = sender as TextBox;
Decimal i;
if (t == null)
return;
if (!Decimal.TryParse(t.Text, out i))
return;
switch (e.Key)
{
case System.Windows.Input.Key.Up:
if (Keyboard.Modifiers == ModifierKeys.Shift)
i += GetDoubleValue(t);
else
i += GetSingleValue(t);
break;
case System.Windows.Input.Key.Down:
if (Keyboard.Modifiers == ModifierKeys.Shift)
i -= GetDoubleValue(t);
else
i -= GetSingleValue(t);
break;
case System.Windows.Input.Key.PageUp:
i += GetTripleValue(t);
break;
case System.Windows.Input.Key.PageDown:
i -= GetTripleValue(t);
break;
default:
return;
}
if (BindingOperations.IsDataBound(t, TextBox.TextProperty))
{
try
{
Binding binding = BindingOperations.GetBinding(t, TextBox.TextProperty);
t.Text = (string)binding.Converter.Convert(i, null, binding.ConverterParameter, binding.ConverterCulture);
}
catch
{
t.Text = i.ToString();
}
}
else
t.Text = i.ToString();
}
}
}

I decided to simplify the reply marked as the answer on here to basically 2 lines using a LINQ expression.
e.Handled = !e.Text.All(Char.IsNumber);
base.OnPreviewTextInput(e);

Why don't you just try using the KeyDown event rather than the PreviewKeyDown Event. You can stop the invalid characters there, but all the control characters are accepted. This seems to work for me:
private void NumericKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9);
bool isNumeric =((e.Key >= Key.D0 && e.Key <= Key.D9) && (e.KeyboardDevice.Modifiers == ModifierKeys.None));
bool isDecimal = ((e.Key == Key.OemPeriod || e.Key == Key.Decimal) && (((TextBox)sender).Text.IndexOf('.') < 0));
e.Handled = !(isNumPadNumeric || isNumeric || isDecimal);
}

I use a custom ValidationRule to check if text is numeric.
public class DoubleValidation : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value is string)
{
double number;
if (!Double.TryParse((value as string), out number))
return new ValidationResult(false, "Please enter a valid number");
}
return ValidationResult.ValidResult;
}
Then when I bind a TextBox to a numeric property, I add the new custom class to the Binding.ValidationRules collection. In the example below the validation rule is checked everytime the TextBox.Text changes.
<TextBox>
<TextBox.Text>
<Binding Path="MyNumericProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DoubleValidation/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

public class NumericTextBox : TextBox
{
public NumericTextBox()
: base()
{
DataObject.AddPastingHandler(this, new DataObjectPastingEventHandler(CheckPasteFormat));
}
private Boolean CheckFormat(string text)
{
short val;
return Int16.TryParse(text, out val);
}
private void CheckPasteFormat(object sender, DataObjectPastingEventArgs e)
{
var isText = e.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true);
if (isText)
{
var text = e.SourceDataObject.GetData(DataFormats.Text) as string;
if (CheckFormat(text))
{
return;
}
}
e.CancelCommand();
}
protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
if (!CheckFormat(e.Text))
{
e.Handled = true;
}
else
{
base.OnPreviewTextInput(e);
}
}
}
Additionally you may customize the parsing behavior by providing appropriate dependency properties.

Combining the ideas from a few of these answers, I have created a NumericTextBox that
Handles decimals
Does some basic validation to ensure any entered '-' or '.' is valid
Handles pasted values
Please feel free to update if you can think of any other logic that should be included.
public class NumericTextBox : TextBox
{
public NumericTextBox()
{
DataObject.AddPastingHandler(this, OnPaste);
}
private void OnPaste(object sender, DataObjectPastingEventArgs dataObjectPastingEventArgs)
{
var isText = dataObjectPastingEventArgs.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true);
if (isText)
{
var text = dataObjectPastingEventArgs.SourceDataObject.GetData(DataFormats.Text) as string;
if (IsTextValid(text))
{
return;
}
}
dataObjectPastingEventArgs.CancelCommand();
}
private bool IsTextValid(string enteredText)
{
if (!enteredText.All(c => Char.IsNumber(c) || c == '.' || c == '-'))
{
return false;
}
//We only validation against unselected text since the selected text will be replaced by the entered text
var unselectedText = this.Text.Remove(SelectionStart, SelectionLength);
if (enteredText == "." && unselectedText.Contains("."))
{
return false;
}
if (enteredText == "-" && unselectedText.Length > 0)
{
return false;
}
return true;
}
protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !IsTextValid(e.Text);
base.OnPreviewTextInput(e);
}
}

You can also try using data validation if users commit data before you use it. Doing that I found was fairly simple and cleaner than fiddling about with keys.
Otherwise, you could always disable Paste too!

My Version of Arcturus answer, can change the convert method used to work with int / uint / decimal / byte (for colours) or any other numeric format you care to use, also works with copy / paste
protected override void OnPreviewTextInput( System.Windows.Input.TextCompositionEventArgs e )
{
try
{
if ( String.IsNullOrEmpty( SelectedText ) )
{
Convert.ToDecimal( this.Text.Insert( this.CaretIndex, e.Text ) );
}
else
{
Convert.ToDecimal( this.Text.Remove( this.SelectionStart, this.SelectionLength ).Insert( this.SelectionStart, e.Text ) );
}
}
catch
{
// mark as handled if cannot convert string to decimal
e.Handled = true;
}
base.OnPreviewTextInput( e );
}
N.B. Untested code.

Add this to the main solution to make sure the the binding is updated to zero when the textbox is cleared.
protected override void OnPreviewKeyUp(System.Windows.Input.KeyEventArgs e)
{
base.OnPreviewKeyUp(e);
if (BindingOperations.IsDataBound(this, TextBox.TextProperty))
{
if (this.Text.Length == 0)
{
this.SetValue(TextBox.TextProperty, "0");
this.SelectAll();
}
}
}

Call me crazy, but why not put plus and minus buttons at either side of the TextBox control and simply prevent the TextBox from receiving cursor focus, thereby creating your own cheap NumericUpDown control?

private void txtNumericValue_PreviewKeyDown(object sender, KeyEventArgs e)
{
KeyConverter converter = new KeyConverter();
string key = converter.ConvertToString(e.Key);
if (key != null && key.Length == 1)
{
e.Handled = Char.IsDigit(key[0]) == false;
}
}
This is the easiest technique I've found to accomplish this. The down side is that the context menu of the TextBox still allows non-numerics via Paste. To resolve this quickly I simply added the attribute/property: ContextMenu="{x:Null}" to the TextBox thereby disabling it. Not ideal but for my scenario it will suffice.
Obviously you could add a few more keys/chars in the test to include additional acceptable values (e.g. '.', '$' etc...)

Private Sub Value1TextBox_PreviewTextInput(ByVal sender As Object, ByVal e As TextCompositionEventArgs) Handles Value1TextBox.PreviewTextInput
Try
If Not IsNumeric(e.Text) Then
e.Handled = True
End If
Catch ex As Exception
End Try
End Sub
Worked for me.

Can you not just use something like the following?
int numericValue = 0;
if (false == int.TryParse(yourInput, out numericValue))
{
// handle non-numeric input
}

void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
string sVal = e.Text;
int val = 0;
if (sVal != null && sVal.Length > 0)
{
if (int.TryParse(sVal, out val))
{
e.Handled = false;
}
else
{
e.Handled = true;
}
}
}

Can also use a converter like:
public class IntegerFormatConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int result;
int.TryParse(value.ToString(), out result);
return result;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int result;
int.TryParse(value.ToString(), out result);
return result;
}
}

Related

Textbox Only Accepts Dot( . ) And Dash( - )

I Tried Some Codes But Didnt Work
For Example
I Found This And It Didnt Work:
if (!char.IsControl(e.KeyChar)
&& !char.IsDigit(e.KeyChar)
&& e.KeyChar != '.')
{
e.Handled = true;
}
// only allow one decimal point
if (e.KeyChar == '.'
&& (sender as TextBox).Text.IndexOf('.') > -1)
{
e.Handled = true;
}
You have a very simple, yet understandable error there.
The Handled property of KeyPressEventArgs should be set to true to keep the operating system from further processing the key.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.keypresseventargs?view=netframework-4.8
In other words, set this to true when you want to PREVENT the key.
Therefore, change your code like this to ALLOW further processing when the pressed key fits the conditions.
Please also see how the boolean variables are introduced to make the code readable.
The code below allows
A ( - ) character if it is the first char in the text box
A ( . ) character if it is not the first char and if there are no other dots
Any control characters
And any digits.
Good luck.
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
bool isControl = char.IsControl(e.KeyChar);
bool isDigit = char.IsDigit(e.KeyChar);
bool isDot = e.KeyChar == '.';
bool alreadyHasADot = (sender as TextBox).Text.IndexOf('.') != -1;
bool isHyphen = e.KeyChar == '-';
bool isFirstChar = (sender as TextBox).Text.Length == 0;
bool isAllowed =
isControl ||
isDigit ||
(isDot && !isFirstChar && !alreadyHasADot) ||
(isHyphen && isFirstChar);
if (!isAllowed)
{
e.Handled = true;
}
}

How to only allow Integers into a textbox

I have a voucher application, and when someone wants to create a campaign voucher, one of the fields they have to specify is "Target Audience". Sometimes the person might enter a string, or a variable that is not an int, and the server will just crash. I just want to implement an if statement to see if its NOT a int, and then do something. I have a regular expression, i just dont know how to implement it. Tried many things. (the textbox to validate is 'campaignAudience')
System.Text.RegularExpressions.Regex.IsMatch(campaignAudience.Value, "[ ^ 0-9]");
I have recently needed a similiar solution. Assuming you need an integer (number without decimal point).
public static bool IntegerAndIsANumber(this string val)
{
if (string.IsNullOrEmpty(val) || val.Contains(',') || val.Contains('.'))
return false;
decimal decimalValue;
if (!Decimal.TryParse(val, out decimalValue))
return false;
decimal fraction = decimalValue - (Int64)decimalValue;
if (fraction == 0)
return true;
return false;
}
It checks if given string is an Integer and if it is a number in the first place.
Using:
if(YourString.IntegerAndIsANumber()){
//value is Integer
}
else{
//incorrect value
}
P.S. Also have done Unit testing with this extension method.
Use a custom TextBox that only accepts numbers, add the following to your project, compile then the custom TextBox will appear at the top of the toolbox when your form is shown in the IDE. Add the TextBox to the form and now the user can only enter digits.
using System;
using System.Windows.Forms;
public class numericTextbox : TextBox
{
private const int WM_PASTE = 0x302;
protected override void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e)
{
string Value = this.Text;
Value = Value.Remove(this.SelectionStart, this.SelectionLength);
Value = Value.Insert(this.SelectionStart, e.KeyChar.ToString());
e.Handled = Convert.ToBoolean(Value.LastIndexOf("-") > 0) ||
!(char.IsControl(e.KeyChar) ||
char.IsDigit(e.KeyChar) ||
(e.KeyChar == '.' && !(this.Text.Contains(".")) ||
e.KeyChar == '.' && this.SelectedText.Contains(".")) ||
(e.KeyChar == '-' && this.SelectionStart == 0));
base.OnKeyPress(e);
}
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_PASTE)
{
string Value = this.Text;
Value = Value.Remove(this.SelectionStart, this.SelectionLength);
Value = Value.Insert(this.SelectionStart, Clipboard.GetText());
decimal result = 0M;
if (!(decimal.TryParse(Value, out result)))
{
return;
}
}
base.WndProc(ref m);
}
}
Linq version:
if(campaignAudience.Value.All(x => Char.IsLetter(x)))
{
// text input is OK
}
Regex version:
if(new Regex("^[A-Za-z]*$").Match(campaignAudience.Value).Success)
{
// text input is OK
}
Limit the keypress event of the textbox to digits

MaxLength Property breaks in my custom TextBox

For my WPF apps I have created a couple custom controls based on a TextBox. These include NumericTextBox, WatermarkTextBox, and ReturnTextBox.
Numeric and Watermark inherit from ReturnTextBox, which inherits from TextBox.
When I go to use any of my custom textboxes they work great. The one problem seems to be NumericTextBox and the MaxLength property. The property is now ignored and does not work. There is no code in any of my custom controls overriding or messing with the MaxLength property.
When I use MaxLength on my ReturnTextBox, it works just as you would expect:
<ui:ReturnTextBox MaxLength="35" Width="500" Background="LightYellow" Text="{Binding BrRptCorpName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" TabIndex="2" />
However when I use the property on my NumericTextBox it is ignored and does not work:
<ui:NumericTextBox MaxLength="9" Background="LightYellow" Text="{Binding BrRptAmt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" TabIndex="1" />
Can anyone help me figure out why MaxLength stops working? Is it because NumericTextBox does not directly inherit from TextBox? Should I be overriding MaxLength property in ReturnTextBox so it can be used by Numeric?
UPDATED WITH CODE
ReturnTextBox class:
public class ReturnTextBox : TextBox
{
static ReturnTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ReturnTextBox), new FrameworkPropertyMetadata(typeof(ReturnTextBox)));
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Return)
{
e.Handled = true;
MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
base.OnPreviewKeyDown(e);
}
}
NumericTextBox
public class NumericTextBox : ReturnTextBox
{
#region Base Class Overrides
static NumericTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(typeof(NumericTextBox)));
}
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type",
typeof(NumericType), typeof(NumericTextBox), new UIPropertyMetadata (NumericType.Integer));
public static readonly DependencyProperty SelectAllOnGotFocusProperty = DependencyProperty.Register("SelectAllOnGotFocus",
typeof(bool), typeof(NumericTextBox), new PropertyMetadata(false));
[Description("Numeric Type of the TextBox"), Category("Common Properties")]
public NumericType Type
{
get { return (NumericType)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
[Description("Select text on focus"), Category("Common Properties")]
public bool SelectAllOnGotFocus
{
get
{
return (bool)GetValue(SelectAllOnGotFocusProperty);
}
set
{
SetValue(SelectAllOnGotFocusProperty, value);
}
}
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
Text = ValidateValue(Type, Text);
bool isValid = IsSymbolValid(Type, e.Text);
e.Handled = !isValid;
if (isValid)
{
int caret = CaretIndex;
string text = Text;
bool textInserted = false;
int selectionLength = 0;
if (SelectionLength > 0)
{
text = text.Substring(0, SelectionStart) + text.Substring(SelectionStart + SelectionLength);
caret = SelectionStart;
}
if (e.Text == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
{
while (true)
{
int ind = text.IndexOf(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator);
if (ind == -1)
break;
text = text.Substring(0, ind) + text.Substring(ind + 1);
if (caret > ind)
caret--;
}
if (caret == 0)
{
text = "0" + text;
caret++;
}
else
{
if (caret == 1 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign)
{
text = NumberFormatInfo.CurrentInfo.NegativeSign + "0" + text.Substring(1);
caret++;
}
}
if (caret == text.Length)
{
selectionLength = 1;
textInserted = true;
text = text + NumberFormatInfo.CurrentInfo.NumberDecimalSeparator + "0";
caret++;
}
}
else if (e.Text == NumberFormatInfo.CurrentInfo.NegativeSign)
{
textInserted = true;
if (Text.Contains(NumberFormatInfo.CurrentInfo.NegativeSign))
{
text = text.Replace(NumberFormatInfo.CurrentInfo.NegativeSign, string.Empty);
if (caret != 0)
caret--;
}
else
{
text = NumberFormatInfo.CurrentInfo.NegativeSign + Text;
caret++;
}
}
if (!textInserted)
{
text = text.Substring(0, caret) + e.Text +
((caret < Text.Length) ? text.Substring(caret) : string.Empty);
caret++;
}
try
{
double val = Convert.ToDouble(text);
double newVal = val;//ValidateLimits(GetMinimumValue(_this), GetMaximumValue(_this), val);
if (val != newVal)
{
text = newVal.ToString();
}
else if (val == 0)
{
if (!text.Contains(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator))
text = "0";
}
}
catch
{
text = "0";
}
while (text.Length > 1 && text[0] == '0' && string.Empty + text[1] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
{
text = text.Substring(1);
if (caret > 0)
caret--;
}
while (text.Length > 2 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign && text[1] == '0' && string.Empty + text[2] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
{
text = NumberFormatInfo.CurrentInfo.NegativeSign + text.Substring(2);
if (caret > 1)
caret--;
}
if (caret > text.Length)
caret = text.Length;
Text = text;
CaretIndex = caret;
SelectionStart = caret;
SelectionLength = selectionLength;
e.Handled = true;
}
base.OnPreviewTextInput(e);
}
private static string ValidateValue(NumericType type, string value)
{
if (string.IsNullOrEmpty(value))
return string.Empty;
value = value.Trim();
switch (type)
{
case NumericType.Integer:
try
{
Convert.ToInt64(value);
return value;
}
catch
{
}
return string.Empty;
case NumericType.Decimal:
try
{
Convert.ToDouble(value);
return value;
}
catch
{
}
return string.Empty;
}
return value;
}
private static bool IsSymbolValid(NumericType type, string str)
{
switch (type)
{
case NumericType.Decimal:
if (str == NumberFormatInfo.CurrentInfo.NegativeSign ||
str == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
return true;
break;
case NumericType.Integer:
if (str == NumberFormatInfo.CurrentInfo.NegativeSign)
return true;
break;
case NumericType.Any:
return true;
}
if (type.Equals(NumericType.Integer) || type.Equals(NumericType.Decimal))
{
foreach (char ch in str)
{
if (!Char.IsDigit(ch))
return false;
}
return true;
}
return false;
}
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (SelectAllOnGotFocus)
SelectAll();
}
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (!IsKeyboardFocused && SelectAllOnGotFocus)
{
e.Handled = true;
Focus();
}
base.OnPreviewMouseLeftButtonDown(e);
}
As you can see I never override or make any changes to the MaxLength property in either of these two controls. However the MaxLength works with the ReturnTextBox and does not work with the NumericTextBox.
Thank you for your help!
e.Handled = !isValid and if(isValid) { e.Handled = true } are your problem. When you use the PreviewSomeEvent the they are tunneling. That means they go from the root and down to the source that caused the event. Setting the Handled property on the event means that other handlers in the events route won't be raised. MaxLength is something defined on the TextBox or at least on something more "primite" than your NumericTextBox.
What happens is that if the users input fail your validation, the event won't be raised "deeper down the tunnel" to check the length: e.Handled = !isValid. And if it passes your validation it wont be raised either because e.Handled = true.
Perhaps a useful analogy can be overriding a method and calling base or not.
For more on events check here

Masked TextBox with decimal numbers

In my window application I need masked textbox which accept real decmal numbers.
eg.
1) 1.56
2) 22.34
3) 123.34
4) 12312.34
This all value should be valid.
Can anyone tell me how can I do this?
And ya if anyone have better solution for real decimal numbers, instead of this masked TextBox
than I love to see it.
Thanks...
Instead of using a MaskedTextbox, consider using NumericUpDown instead. It supports System.Decimal numbers which supports most real-set numbers you should be caring about.
The NumericUpDown.DecimalPlaces property supports up to 99 decimal places.
Use a custom control like this one (modify it to fulfill your needs):
using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace CustomControls
{
public enum PasteRejectReasons
{
Unknown = 0,
NoData,
InvalidCharacter,
Accepted
}
public class DecimalTextBox : TextBox
{
public const int WM_PASTE = 0x0302;
public event EventHandler<KeyRejectedEventArgs> KeyRejected;
public event EventHandler<PasteEventArgs> PasteRejected;
private bool _DecimalSeparator = false;
private int _Precision;
public new HorizontalAlignment TextAlign
{
get { return base.TextAlign; }
set { base.TextAlign = value; }
}
public int Precision
{
get { return _Precision; }
set { _Precision = value; }
}
public DecimalTextBox()
{
TextAlign = HorizontalAlignment.Right;
Precision = 3;
}
protected override void OnGotFocus(EventArgs e)
{
SelectAll();
base.OnGotFocus(e);
}
protected override void OnKeyDown(KeyEventArgs e)
{
bool validate = true;
if (Text.Contains(".") || Text.Contains(","))
{
int indexSep;
string[] split;
string partiDecimal = "";
if (Text.Contains("."))
indexSep = Text.IndexOf('.');
else
indexSep = Text.IndexOf(',');
split = Text.Split(new char[] { ',', '.' });
partiDecimal += split[1];
if (partiDecimal.Length >= Precision)
if (SelectionStart > Text.Length - (partiDecimal.Length + 1))
validate = false;
}
bool result = true;
bool validateKeys = (e.KeyCode == Keys.Enter);
bool numericKeys = (
((e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9) ||
(e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9))
&& e.Modifiers != Keys.Shift
&& validate);
bool ctrlA = e.KeyCode == Keys.A && e.Modifiers == Keys.Control;
bool editKeys = (
(e.KeyCode == Keys.Z && e.Modifiers == Keys.Control) ||
(e.KeyCode == Keys.X && e.Modifiers == Keys.Control) ||
(e.KeyCode == Keys.C && e.Modifiers == Keys.Control) ||
(e.KeyCode == Keys.V && e.Modifiers == Keys.Control) ||
e.KeyCode == Keys.Delete ||
e.KeyCode == Keys.Back);
bool navigationKeys = (
e.KeyCode == Keys.Up ||
e.KeyCode == Keys.Right ||
e.KeyCode == Keys.Down ||
e.KeyCode == Keys.Left ||
e.KeyCode == Keys.Home ||
e.KeyCode == Keys.End);
bool decimalSeparator = ((
e.KeyCode == Keys.Decimal ||
e.KeyValue == 190 ||
e.KeyValue == 188)&&
TextLength != 0 &&
SelectionLength == 0);
if (decimalSeparator)
{
if (!_DecimalSeparator)
_DecimalSeparator = true;
else
decimalSeparator = false;
}
if (!(numericKeys || editKeys || navigationKeys || decimalSeparator || validateKeys))
{
if (ctrlA)
SelectAll();
result = false;
}
if (!result)
{
e.SuppressKeyPress = true;
e.Handled = true;
if (!ctrlA)
OnKeyRejected(new KeyRejectedEventArgs(e.KeyCode));
}
else
base.OnKeyDown(e);
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (e.KeyChar == ';' || e.KeyChar == '?')
{
if (!(Text.Contains(",") || Text.Contains(".")))
_DecimalSeparator = false;
e.Handled = true;
}
}
protected override void OnTextChanged(EventArgs e)
{
bool invalid = false;
int i = 0;
foreach (char c in Text) // Check for any non digit characters.
{
if (!(char.IsDigit(c) || c == ',' || c == '.'))
{
invalid = true;
break;
}
if (c == ',' || c == '.')
i++;
}
if (i == 0)
_DecimalSeparator = false;
else if (i > 1)
invalid = true;
if (invalid)
{
Text = "";
return;
}
if (Text.Contains(".") || Text.Contains(","))
{
string charSep = "";
string[] split;
string partiEntier = "";
if (Text.Contains("."))
charSep = ".";
else
charSep = ",";
split = Text.Split(new char[] { ',', '.' });
partiEntier += split[0];
if (partiEntier == "")
Text = "0" + charSep + split[1];
}
base.OnTextChanged(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PASTE)
{
PasteEventArgs e = CheckPasteValid();
if (e.RejectReason != PasteRejectReasons.Accepted)
{
m.Result = IntPtr.Zero;
OnPasteRejected(e);
return;
}
}
base.WndProc(ref m);
}
private PasteEventArgs CheckPasteValid()
{
PasteRejectReasons rejectReason = PasteRejectReasons.Accepted;
string originalText = Text;
string clipboardText = string.Empty;
string textResult = string.Empty;
try
{
clipboardText = Clipboard.GetText(TextDataFormat.Text);
if (clipboardText.Length > 0)
{
textResult = (
Text.Remove(SelectionStart, SelectionLength).Insert(SelectionStart, clipboardText));
foreach (char c in clipboardText)
{
if (!char.IsDigit(c))
{
rejectReason = PasteRejectReasons.InvalidCharacter;
break;
}
}
}
else
rejectReason = PasteRejectReasons.NoData;
}
catch
{
rejectReason = PasteRejectReasons.Unknown;
}
return new PasteEventArgs(originalText, clipboardText, textResult, rejectReason);
}
protected virtual void OnKeyRejected(KeyRejectedEventArgs e)
{
EventHandler<KeyRejectedEventArgs> handler = KeyRejected;
if (handler != null)
handler(this, e);
}
protected virtual void OnPasteRejected(PasteEventArgs e)
{
EventHandler<PasteEventArgs> handler = PasteRejected;
if (handler != null)
handler(this, e);
}
}
}

Convert a System.Windows.Input.KeyEventArgs key to a char

I need to get the event args as a char, but when I try casting the Key enum I get completely different letters and symbols than what was passed in.
How do you properly convert the Key to a char?
This is what I've tried
ObserveKeyStroke(this, new ObervableKeyStrokeEvent((char)((KeyEventArgs)e.StagingItem.Input).Key));
Edit: I also don't have the KeyCode property on the args. I'm getting them from the InputManager.Current.PreNotifyInput event.
See How to convert a character in to equivalent System.Windows.Input.Key Enum value?
Use KeyInterop.VirtualKeyFromKey instead.
It takes a little getting used to, but you can just use the key values themselves. If you're trying to limit input to alphanumerics and maybe a little extra, the code below may help.
private bool bLeftShiftKey = false;
private bool bRightShiftKey = false;
private bool IsValidDescriptionKey(Key key)
{
//KEYS ALLOWED REGARDLESS OF SHIFT KEY
//various editing keys
if (
key == Key.Back ||
key == Key.Tab ||
key == Key.Up ||
key == Key.Down ||
key == Key.Left ||
key == Key.Right ||
key == Key.Delete ||
key == Key.Space ||
key == Key.Home ||
key == Key.End
) {
return true;
}
//letters
if (key >= Key.A && key <= Key.Z)
{
return true;
}
//numbers from keypad
if (key >= Key.NumPad0 && key <= Key.NumPad9)
{
return true;
}
//hyphen
if (key == Key.OemMinus)
{
return true;
}
//KEYS ALLOWED CONDITITIONALLY DEPENDING ON SHIFT KEY
if (!bLeftShiftKey && !bRightShiftKey)
{
//numbers from keyboard
if (key >= Key.D0 && key <= Key.D9)
{
return true;
}
}
return false;
}
private void cboDescription_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftShift)
{
bLeftShiftKey = true;
}
if (e.Key == Key.RightShift)
{
bRightShiftKey = true;
}
if (!IsValidDescriptionKey(e.Key))
{
e.Handled = true;
}
}
private void cboDescription_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftShift)
{
bLeftShiftKey = false;
}
if (e.Key == Key.RightShift)
{
bRightShiftKey = false;
}
}
That work for me:
Based on the last entry i found that in WPF there is no such event PreNotifyInput, but i found and equivalent PreviewTextInput
First I try with a RegExp, but I cant make it work, then I use a simple indexOf.
private bool ValidChar(string _char)
{
string Lista = #" ! "" # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? # A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ";
return Lista.IndexOf(_char.ToUpper()) != -1;
//System.Text.RegularExpressions.Regex RegVal = new System.Text.RegularExpressions.Regex(#"(?<LETRAS>[A-Z]+)+(?<NUMERO>[0-9]+)+(?<CAR>[!|""|#|$|%|&|'|(|)|*|+|,|\-|.|/|:|;|<|=|>|?|#]+)+");
//return RegVal.IsMatch(_char);
}
private void textBoxDescripcion_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (!ValidChar(e.Text))
e.Handled = true;
}
I know this is old, but none of the answers seem to actually answer the question. The reason a different char is coming back is because when you just try to cast it to a char you are casting the enum value to a 'char'. However:
var keyPressed = e.key.ToString();
Works great. Returns the key pressed as a string. Then you check the length. If it's == 1 then it's a char, number or symbol. If it's greater than 1 it's a special key.
If you just want the char you can then do keyPressed[0];
This is how I do it.
private void scrollViewer_KeyDown(object sender, KeyEventArgs e)
{
if (!e.IsRepeat)
{
var keyPressed = e.Key.ToString();
if(keyPressed.Length == 1)
CharKeyPressed(keyPressed[0]);
else if(keyPressed.Length > 1)
HandleSpecialKey(keyPressed)
}
}
Inside your PreNotifyInput handler, try something like this:
if (e.StagingItem.Input is System.Windows.Input.TextCompositionEventArgs)
{
if (!String.IsNullOrEmpty((e.StagingItem.Input as System.Windows.Input.TextCompositionEventArgs).Text))
{
Char c = (e.StagingItem.Input as System.Windows.Input.TextCompositionEventArgs).Text[0];
}
}
It raises multiple times for the different routed events, so you may want to filter for a particular one.

Categories