Posts tagged ‘Event’

How to limit or prevent characters in a TextBox in C#? Or How to create a NumberTextBox or DigitBox object?

Let say you want to have a TextBox in which you only want to allow integers (0-9) or maybe you only want to allow strings, A-Za-z.

Well, lets play around with this for a second and see what we can do.

To get started do this:

  1. Create a new WPF Application project.
  2. In the designer, add a TextBox from the Toolbox.
  3. In the properties field for the TextBox click the icon that look like a lightening bolt to bring up events.
  4. Find the KeyDown event and double-click on it.

Ok, so you should now have the following function:

[sourcecode language=”csharp”]
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
}
[/sourcecode]

The key that was pressed is accessible from the KeyEventArgs variable, e. Specifically e.Key.

Key just happens to be an enum, which means they can basically be treated as integers.

The key press can be ignored by telling setting e.Handled=true. This way it is already marked as handled and will not be added to the TextBox.

Allow only number keys 0-9 in a TextBox

Here is a simple function to allow only natural numbers or number keys 0-9 in a TextBox. Be aware that the keys may be different in other languages.

[sourcecode language=”csharp”]
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key < Key.D0 || e.Key > Key.D9)
{
e.Handled = true;
}
}
[/sourcecode]

Wow, that was pretty simple, right?  WRONG! It is not that easy.

You realize that there are two sets of numbers on a keyboard right? You have numbers in row above your QWERTY keys and you likely have a Number pad on the right of your keyboard as well.  That is not all either.

What else did we forget, you might ask?  Well, of the seven requirements we need to handle, we only handled one.  For an application to be considered release quality or enterprise ready or stable, all seven of these should be handled.

  1. Numbers 1234567890 above QWERTY keys.
  2. Numpad numbers.
  3. You may want to allow pressing of the Delete, Backspace, and Tab keys.
  4. What about pasting?
  5. What about drag and drop?
  6. What about someone else calling your code and changing the text?
  7. Mnemonics should work.

Requirements

Here are the six requirements in clear statements.

  1. Allow pressing Numbers 1234567890 above QWERTY keys.
  2. Allow pressing Numpad numbers.
  3. Allow pressing of the Delete, Backspace, and Tab keys.
  4. Allow pasting so that only numbers in a string are added: A1B2C3 becomes 123.
  5. Allow drag and drop so that only numbers in a string are added: A1B2C3 becomes 123.
  6. Allow change in code at runtime so that only numbers in a string are added: A1B2C3 becomes 123.
  7. When another control has a mnemonic, such as Alt + S, pressing Alt + S, should property change the focus and place the cursor in the Alt + S control.

Remembering lists like this is something that comes with experience.  If you thought of these on your own, good work.  If you didn’t think of them on your own, don’t worry, experience comes with time.

So lets enhance this to handle each of these.

Handling both Number keys and Numpad keys

[sourcecode language=”csharp”]
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
e.Handled = !IsNumberKey(e.Key);
}

private bool IsNumberKey(Key inKey)
{
if (inKey < Key.D0 || inKey > Key.D9)
{
if (inKey < Key.NumPad0 || inKey > Key.NumPad9)
{
return false;
}
}
return true;
}
[/sourcecode]

All right, we now have two of the six requirements down.

Handling Delete, Backspace, and Tab, and Mnemonics

You can probably already guess how easy it will be to do something similar to handle these two keys.

[sourcecode language=”csharp”]
protected void OnKeyDown(object sender, KeyEventArgs e)
{
e.Handled = !IsNumberKey(e.Key) && !IsActionKey(e.Key);
}

private bool IsNumberKey(Key inKey)
{
if (inKey < Key.D0 || inKey > Key.D9)
{
if (inKey < Key.NumPad0 || inKey > Key.NumPad9)
{
return false;
}
}
return true;
}

private bool IsActionKey(Key inKey)
{
return inKey == Key.Delete || inKey == Key.Back || inKey == Key.Tab || inKey == Key.Return || Keyboard.Modifiers.HasFlag(ModifierKeys.Alt);
}
[/sourcecode]

Ok, now we have four of six requirements handled.

Handling Paste (Ctrl + V) and Drag and Drop

Yes, I can handle both at the same time with a new event TextChanged.

This is setup so that if someone pastes both letters and number, only the numbers are pasted: A1B2C3 becomes 123.

This event is not configured so we have to set it up.

  1. In the designer, click the TextBox.
  2. In the properties field for the TextBox click the icon that look like a lightening bolt to bring up events.
  3. Find the TextChanged event and double-click on it.

You should now have this stub code for the event function.

[sourcecode language=”csharp”]

private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
}
[/sourcecode]

Here is some easy code to make sure each character is actually a digit.

[sourcecode language=”csharp”]
protected void OnTextChanged(object sender, TextChangedEventArgs e)
{
base.Text = LeaveOnlyNumbers(Text);
}

private string LeaveOnlyNumbers(String inString)
{
String tmp = inString;
foreach (char c in inString.ToCharArray())
{
if (!IsDigit(c))
{
tmp = tmp.Replace(c.ToString(), "");
}
}
return tmp;
}

public bool IsDigit(char c)
{
return (c >= ‘0’ && c <= ‘9’);
}
[/sourcecode]

Guess what else? This last function actual handles the first five requirements all by itself. But it is less efficient so we will leave the previous requirements as they are.

Handling Direct Code Change

Ok, so some somehow your TextBox is passed inside code during runtime a string that contains more than just numbers.  How are you going to handle it.

This is setup so that if someone pastes both letters and number, only the numbers are pasted: A1B2C3 becomes 123.  Well, we need to run the same function as for Drag and Drop, so to not duplicate code, it is time to create a class or object.

Creating a NumberTextBox object

Now we need to make our code reusable. Lets create a class called NumberTextBox and it can do everything automagically.

NumberTextBox

[sourcecode language=”csharp”]
using System;
using System.Windows.Controls;
using System.Windows.Input;

namespace System.Windows.Controls
{
public class DigitBox : TextBox
{
#region Constructors
/// <summary>
/// The default constructor
/// </summary>
public DigitBox()
{
TextChanged += new TextChangedEventHandler(OnTextChanged);
KeyDown += new KeyEventHandler(OnKeyDown);
}
#endregion

#region Properties
new public String Text
{
get { return base.Text; }
set
{
base.Text = LeaveOnlyNumbers(value);
}
}

#endregion

#region Functions
private bool IsNumberKey(Key inKey)
{
if (inKey < Key.D0 || inKey > Key.D9)
{
if (inKey < Key.NumPad0 || inKey > Key.NumPad9)
{
return false;
}
}
return true;
}

private bool IsActionKey(Key inKey)
{
return inKey == Key.Delete || inKey == Key.Back || inKey == Key.Tab || inKey == Key.Return || Keyboard.Modifiers.HasFlag(ModifierKeys.Alt);
}

private string LeaveOnlyNumbers(String inString)
{
String tmp = inString;
foreach (char c in inString.ToCharArray())
{
if (!IsDigit(c))
{
tmp = tmp.Replace(c.ToString(), "");
}
}
return tmp;
}

public bool IsDigit(char c)
{
return (c >= ‘0’ && c <= ‘9’);
}
#endregion

#region Event Functions
protected void OnKeyDown(object sender, KeyEventArgs e)
{
e.Handled = !IsNumberKey(e.Key) && !IsActionKey(e.Key);
}

protected void OnTextChanged(object sender, TextChangedEventArgs e)
{
base.Text = LeaveOnlyNumbers(Text);
}
#endregion
}
}
[/sourcecode]

Now I can delete the events and functions from the Window1.xaml.cs file. I don’t have to add any code to the Window1.xaml.cs. Instead I need to reference my local namespace in the Window1.xaml and then change the TextBox to a local:NumberTextBox. Here is the XAML.

[csharp]
<Window x:Class="TextBoxIntsOnly.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextBoxIntsOnly"
Title="Window1" Height="300" Width="300">
<Grid>
<local:DigitBox Margin="87,27,71,0" VerticalAlignment="Top" x:Name="textBox1" />
<Label Height="28" HorizontalAlignment="Left" Margin="9,25,0,0" Name="label1" VerticalAlignment="Top" Width="72">Integers:</Label>
<TextBox Height="23" Margin="87,56,71,0" Name="textBox2" VerticalAlignment="Top" />
<Label Height="28" HorizontalAlignment="Left" Margin="9,54,0,0" Name="label2" VerticalAlignment="Top" Width="72">Alphabet:</Label>
</Grid>
</Window>
[/csharp]

And now all seven requirements are met.