Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

windows phone 8.1 textbox character virtualKey validation

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:

 private void NumKeyDown(object sender, KeyRoutedEventArgs e)
        {

           bool isNumber = (e.Key == Windows.System.VirtualKey.Number0 ||
                 e.Key == VirtualKey.Number1 ||
                 e.Key == VirtualKey.Number2 ||
                 e.Key == VirtualKey.Number3 ||
                 e.Key == VirtualKey.Number4 ||
                 e.Key == VirtualKey.Number5 ||
                 e.Key == VirtualKey.Number6 ||
                 e.Key == VirtualKey.Number7 ||
                 e.Key == VirtualKey.Number8 ||
                 e.Key == VirtualKey.Number9 ||
                 e.Key == VirtualKey.Back);

            CoreVirtualKeyStates shiftState = Window.Current.CoreWindow.GetKeyState(VirtualKey.Shift);


            bool shiftIsNotDown = true;
            if ((shiftState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down)
                shiftIsNotDown = false;

            e.Handled = !(isNumber && shiftIsNotDown);

        }

it works but there are two major flaws :

1- it also accept the shifted-characters( i.e !@#$%^&*) while that is not wanted.

2- when I start the app the textbox is filled with a number. I can input any number or characters(!@#$%^), also I can delete using the BACKSPACE. during this time the ShiftState is NONE. However once I delete all content in the textbox using the BACKSPACE the ShiftState changes to [ locked | Down ] and I become unable to input anything in that text box.

notice that i never touch the shift button !

where am I wrong here??

UPDATE

I discovered that using the swipe feature will input texts letters without triggering the keyDown event I was applying :(

any help is appreciated

like image 906
stackunderflow Avatar asked Feb 13 '23 13:02

stackunderflow


1 Answers

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

  1. 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++.

  2. regarding testing my application. I used 3 [[different]] input methods. as the following picture :

keyBoards

and the result was different and incosistant with the KeyDown event as follow :

let me assume that I want to input the character (&)

  • on Emulator keyboard : keyDown eventArg e.Key = Number7.
  • on the PC touch keyboard : the KeyDown event will fire TWICE. in the first time the e.Key code will be shift, and in the second keyDown event the e.Key will be Number7
  • on the physical keyboard ofcourse two keyDown events will fire bec. you have to press shift first then Number7 to get the (&).

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:

  • wheneve you get the focus to an empty textbox the Shift button will get into the status of Locked ( to start you sentence with a capital letter). so any keyDown event will fire first the Shift key then the letter you pressed. this might confuse you if you were not aware about it .

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;
    }

:)

like image 160
stackunderflow Avatar answered Feb 15 '23 03:02

stackunderflow