im develeoping windows phone 8.1 app. In a textbox I want to prevent the user from inputting any non-digital letter only [0-9].
So here is my code:
Here is a post about a similar topic from the MSDN forums. There appears to be no good way to prevent input, though you could handle it after the fact. The suggested guideline is to allow invalid input but don't do anything with invalid input. For example allow the user to enter a character but don't allow them to submit the form..I don't necessarily agree but if you have to prevent input the CoreWindow.AcceleratorKeyActivated happens 'too soon to tell what character it is.' In your case you're just looking for numbers so I think it should be fine.
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/3c0f5251-9356-4f1d-a148-57024ae71724/testing-for-valid-numeric-and-currency-key-strokes?forum=winappswithcsharp
I ran your code and don't notice the Shift key state switches when the textbox is removed.
I also used the on-screen keyboard and did receive the KeyDown event.
Also, your code did not accept the shifted characters either !@#$%.
In Summary I could not reproduce any of the bugs reported in the post.
Subscribe to the CoreWindow.AcceleratorKeyActivated event. You can check the VirtualKey. This event happens before TextBox.TextChanged so we use this to leverage the routed event framework by filtering which key presses the textbox will handle.
public MainPage()
{
this.InitializeComponent();
Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated += Dispatcher_AcceleratorKeyActivated;
}
void Dispatcher_AcceleratorKeyActivated(CoreDispatcher sender, AcceleratorKeyEventArgs args)
{
if (MyTextBox.FocusState != FocusState.Unfocused)
{
if (args.VirtualKey == VirtualKey.Number0 ||
args.VirtualKey == VirtualKey.Number1 ||
args.VirtualKey == VirtualKey.Number2 ||
args.VirtualKey == VirtualKey.Number3 ||
args.VirtualKey == VirtualKey.Number4 ||
args.VirtualKey == VirtualKey.Number5 ||
args.VirtualKey == VirtualKey.Number6 ||
args.VirtualKey == VirtualKey.Number7 ||
args.VirtualKey == VirtualKey.Number8 ||
args.VirtualKey == VirtualKey.Number9)
{
args.Handled = false;
}
else
{
args.Handled = true;
}
}
}
Ok, I will try to answer this question based on the responses I received here and based on the research I did for the last couple of days. answer will be two sections [ Side Notes - solution ]
You may jump to (Solution section )if you are not interested in the details of the problems i found interesting to share with readers
Side Notes:
I have noticed the following problems
the most important is that window 8.1 and xaml and winRT documentation is extremely poor, and almost useless. I hope someone from Microsoft winRT team is reading this. Unless they are doing this on purpose to force most of the developers to go to C++.
regarding testing my application. I used 3 [[different]] input methods. as the following picture :
and the result was different and incosistant with the KeyDown event as follow :
let me assume that I want to input the character (&)
also I noticed on the physical keyboard that regardless of which shift you press (i.e. right or left) the KeyDown event e.Key
will show LeftShift
!!
I don't know if this was special case to my computer keyboard , but all these findings are to show that keyDown is not really trustable and documentation is lacking here
Also another finding I noticed that I couldn't control:
I would like to thank @Bryan Stump, for taking me to the microsoft link MSFT Forum that showed me important information to understand the situation:
"Tracking KeyUp and KeyDown as Shiva's code suggests will work for limited cases, but not all. For example, it won't work with ink or (I think) IME input. It also makes assumptions about keyboard layout which are not valid for all keyboards. Unless you can limit your requirements to very specific, basic scenarios the only way to successfully limit the input is to do so after the fact." Rob Caplan [MSFT]
This link assured me that the only avaiable way is to accept the character then remove it if not suiting your validation, hence the quote :"to do so after the fact".
and finally I would like to thank @Hans Passant for his short comment that put me on the right track:
"Use CoreWindow.CharacterReceived instead".
after that I started to search and the only good example I found regarding the CoreWindow.CharacterReceived is on StackOverflow
and from there I started my solution as follow.
Solution:
Introduction:
First: you can't intercept the character and prevent it from reaching the textbox.
Second: you can't use the keyDown or keyUp events to know what is the character. you can only have an idea about the key pressed not the character resulted.
Third: the event that will give you the character recieve is named
"CoreWindow.CharacterReceived", but notice that you will know the
character after it is written to the textbox. it is at this point you
can choose to accept it or remove it
Fourth: since the character is recieved in the textBox then the
proper way to deal with it is the event textChanged.
Fifth: and most important; is that the CharacterReceived
event will
fire in a loop on each letter in the word , and this needs special maneuver and validation
so based on the Five facts above the pseudoCode will be:
rely on RegEx to validate the text and accept it; otherwise, if input was invalid then resume the previous state of the textBox.Text
string txtTemp = "";
private void changedText(object sender, TextChangedEventArgs e)
{
Regex regex = new Regex(@"^\d{1,4}$");
string txtToTest = txtNumber.Text;
if (regex.IsMatch(txtToTest)|| txtNumber.Text=="")
{
//do nothing
}
else
{
txtNumber.Text = txtTemp;
txtNumber.Select(txtNumber.Text.Length, 0);
}
//Save the current value to resume it if the next input was invalid
txtTemp = txtNumber.Text;
}
The above solution suited my case where i want to make sure that user will input numbers only. However there are cases that you want to make sure the user will input specific letter and you need to respond based on the letter pressed !! In such case you will need the following solution which is incomplete and lacking all possible scenario where the user might enter a letter from the clipboard (Paste ) or using the swype functionality of the keyboard.
Here the solution for the scenario where you need to control the input letter by letter (key by key):
1- since CoreWindow.CharacterReceived
event is not specific to textBox ( it is window/page event). so you will wire it up whenever your textbox got focus. and unwire it whenever your textbox lose focus.
2- listen to the keyDow event
. whenever it is fired, save the textBox.Text
value to a temporary variable txtTemp
.
3- set a boolean indicating that the character recieved is accepted or not (bool acceptChange = true
) . and using the CoreWindow.CharacterReceived event set this boolean to true or false (accepted / not accepted)
4- in the textChange event if the bool acceptChange is true, then do nothing. If the bool acceptChange is false then reset the textBox.Text value to the temporary value you saved during the keyDown event ( txtBox.Text = txtTemp )
with this solution we can make sure that we accept only the character we want, with only one tiny problem remaining as the following:
suppose you set up your validation rules to accept only numbers. and textBox.Text = "752". if the user enter letter "v" the txtTemp will be "752" and the new value for txtBox.Text will be "752v" and on textChange event we will reset the value to the previous value (i.e "752"). this is done by the help of the keydown event.
but what if the user didn't type the letter "v" but he copied it from another place and used the paste function then the new value of txtBox.Text ="752v", but the txtTemp will be "75" because the keYDown even was not triggered to capture the latest txtBox value :(
it is here that the importance of textBox event "paste" shows up.
so step 5 in my pseudocode is:
5- in the txtBox.paste
event make sure that you cancel this event by making e.Handled=true;
and now I come to the code :
//this is critical to wire up the "Window.Current.CoreWindow.CharacterReceived" event when
//the textBox get focus and to unwire it when the textBox lose focus.
// notice that the whole page is listening not only the textBox
private void txtBox_GotFocus(object sender, RoutedEventArgs e)
{
Window.Current.CoreWindow.CharacterReceived += inputEntered;
}
private void txtBox_LostFocus(object sender, RoutedEventArgs e)
{
Window.Current.CoreWindow.CharacterReceived -= inputEntered;
}
// temporary variable for holding the latest textBox value before the textChange event is trigerred
string txtTemp = "";
private void txtBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
//whenever a key is pressed, capture the latest textBox value
txtTemp= txtBox.Text;
}
// this boolean is to be used by the textChanged event to decide to accept changes or not
bool acceptChange = true;
// here we recieve the character and decide to accept it or not.
private void inputEntered(CoreWindow sender, CharacterReceivedEventArgs args)
{
// reset the bool to true in case it was set to false in the last call
acceptChange = true;
Debug.WriteLine("KeyPress " + Convert.ToChar(args.KeyCode)+ "keyCode = "+ args.KeyCode.ToString());
args.Handled = true;
//in my case I needed only numeric value and the backSpace button
if ((args.KeyCode > 47 && args.KeyCode < 58) || args.KeyCode == 8)
{
//do nothing (i.e. acceptChange is still true)
}
else
{
//set acceptChange to false bec. character is not numeric nor backSpace
acceptChange = false;
}
}
private void txtBox_TextChanged(object sender, TextChangedEventArgs e)
{
//the code here is my validation where I want only 3 digits number with no decimal
if (txtBox.Text.Length < 4)
{
if (acceptChange)
{
// do nothing
}
else
{
txtBox.Text = txtTemp;
//this is to move the cursor to the end of the text in the textBox
txtBox.Select(txtBox.Text.Length, 0);
}
}
else
{
txtBox.Text = txtTemp;
//this is to move the cursor to the end of the text in the textBox
txtBox.Select(txtBox.Text.Length, 0);
}
}
// this is for the special case where the user input text using Paste function
private void txtBox_Paste(object sender, TextControlPasteEventArgs e)
{
e.Handled=true;
}
:)