Solved

Recording keyboard key strokes

Posted on 2011-03-21
23
2,034 Views
Last Modified: 2013-12-17
Hi Experts.

I'm a bit stuck here so I would really appreciate some help.

I'm working on a macro program that will forward certain keyboard key strokes to other keys. So let's say if I press "T" I want "U" to be returned instead.
My GUI has a text box that users can click in and specify which button will that "T" key press be forwarded to. Key press event :

        private void txtTrigger_KeyPress(object sender, KeyPressEventArgs e)
        {
              txtTrigger.Text = e.KeyChar.ToString();
        }
Then I save that Textbox's INT ascii value to the XML file as [112] and next time a program user hits "T" - 112 gets converted back to a string and sent back:

ConfigFile.Trigger = Char.ConvertToUtf32(txtTrigger.Text,0);

SendKeys.SendWait(SpellToTrigger);

Awesome, works great with LETTERS ONLY.
 
I can't figuire out how to record and return other keys such as F1-12, CTRL, ALT, TAB, Backpsace...etc. They don't seem to trigger TextBox KeyPress events at all. Do I really have to go though every single Function and other keys on my keyboard wtih if (e.KeyCode == Keys.F9) individually ?

I tried using the KeyDown instead of KeyPress event and Function key presses do get picked up but KeyValues returned back to a user are incorrect:

            int pressedKey = e.KeyValue;
            txtTrigger.Text = char.ConvertFromUtf32(pressedKey);

I press F8 and W appears in that txtTrigger. F5 shows up as letter T.
Shift, Control and some other keys appear as .

Any help ? All I want to do is setup something like you might have seen in computer games when you setup your input bindings where you specify what key your action is forwared to.

Thank you in advance.
0
Comment
Question by:techsuppoprt
  • 11
  • 10
  • 2
23 Comments
 
LVL 8

Expert Comment

by:mac-will
ID: 35184480
You could use a lower level keyboard hook.

http://blogs.msdn.com/b/toub/archive/2006/05/03/589423.aspx
0
 
LVL 12

Expert Comment

by:Mohamed Abowarda
ID: 35185843
If you want to record the keystrokes even if your program is not active on the screen, you need to use GetAsyncKeyState API to frequently check each key state:
http://msdn.microsoft.com/en-us/library/ms646293(v=vs.85).aspx
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35191308
Thanks.
I don't think you guys read my question at all though or was it a bit confusing possibly ?

Let me try to rephrase it.
What's the best way to store captured key strokes in the XML file ?

Right now I'm saving the INT keyvalues and doing so won't let me properly capture Function keys and other buttons such as Backspase, Delete, Shift, ALT, Caps Lock .. etc.

I want to be able to capture and record ( in the XML config file ) any key on my keyboard to later that key press be simulated back to the user with something like
SendKeys.SendWait().
0
 
LVL 8

Expert Comment

by:mac-will
ID: 35193121
This still is not clear to me.

Do you want to capture the keypress event of a textbox or key presses in general from the application?  Obviously pressing shift does not do anything in the keypress event of a textbox since you cannot display a <shift> for example.

Perhaps you could simply use the keyDown or KeyUp events? As these events are triggered even for control keys.

From MSDN:

"The KeyPress event is not raised by noncharacter keys; however, the noncharacter keys do raise the KeyDown and KeyUp events."

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.keypress.aspx

0
 
LVL 8

Expert Comment

by:mac-will
ID: 35193213
If it helps try adding this handler to see what king of info is printed when you press a key.

        private void txtTrigger_KeyDown(object sender, KeyEventArgs e)
        {
            Console.WriteLine("KeyCode : " + e.KeyCode.ToString() + "    Modifiers : " + e.Modifiers.ToString());
        }

Open in new window


And as for storing the info in an xml file why not just store the keycode (as a string)

Here is the back and forth from string to keycode:

Keys mySavedValue = (Keys)Enum.Parse(typeof(Keys), "Alt");

String myValueToSave = e.KeyCode.ToString();

Open in new window

0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35193300
What I want:
- I have a form with a button and a text box;
- User activates a form text box;
- Once the textbox is active - user presses F6
- "F6" shows up in the textbox control and something gets saves to the XML file
- User presses the button - "F6" key press is simulated.

Does it make sense ?

My problem is not HOW to capture. I know how to do it through Keypress/KeyDown events... it's WHAT to capture. What can I record to the XML file to easily re-play any button on the keyboard, inluding Function, extra keys and key combination ?

I want my program to read the XML file and recognize what key press actions to send back to the user.
0
 
LVL 8

Expert Comment

by:mac-will
ID: 35193477

I will assume that the "User presses the button ..." simulation is for the textbox.


So just like my example above but combining both steps:

        private void txtTrigger_KeyDown(object sender, KeyEventArgs e)
        {
             if( check if its a printable keycode)
             {
                  txtTrigger.Text += e.KeyCode.ToString();
             }
            MySaveToXMLFunction(e.KeyCode.ToString());
        }

Open in new window



To Send a key you could use the SendKeys class or if you need more control use the Windows API:

  private void btnSend_Click(object sender, EventArgs e)
        {
            txtTrigger.Select();
            SendKeys.Send("{" + Keys.F6.ToString() + "}");
       }

Open in new window


Obviously the Keys.F6.ToString() would be whatever you retrieved from you xml file....


0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35194689

Would you say it's safe to go based on this chart:

http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.send.aspx

and simply throw all my saved KeyCode values in SendKey.Send( { SavedKeyCode } ) whenever I need to send that saved key back to the user ?

I assume I'll check for SHIFT/ALT/CTR pressed and modify my saved XML KeyCode with + % or ^ accordingly ?
Going to try it...Let me know if it sounds wrong.
0
 
LVL 8

Expert Comment

by:mac-will
ID: 35198233
That sounds right.  

That is what this class (SendKeys) was made for.  If this meets your needs in terms of what control is receiveing the keys etc. than definitely go this way.  

If for example you want to send keys to the desktop or receive keys when your application does not have focus you would probably want to go with a keyboard hook.
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35199922
Errr...Doesn't really work as some KeyCode string values don't match with the actual keys. ( NumPad5 , D6 .. etc). SendKeys.SendWait("NumPad8") actually returns "NumPad8" instead of simulating number 8 being pressed on the NumPad.

Is there  a way to cast/convert KeyEventArgs.KeyCode back to Keys enum and send a keypress event that way ?

Thank you in advance.
ps. I added points.
0
 
LVL 8

Expert Comment

by:mac-will
ID: 35200346
Ok so no, not really.  there is no magic you either have to map the Keys enum to the table in the link above or more easily use the Windows API.

(Are you familiar with PInvoke?)

Here is the all the code you need:
(using System.Runtime.InteropServices;)

        [DllImport("user32.dll")]
        static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);

        void PressKey(byte keyCode)
        {
            const int KEYEVENTF_EXTENDEDKEY = 0x1;
            const int KEYEVENTF_KEYUP = 0x2;
            keybd_event(keyCode, 0x45, KEYEVENTF_EXTENDEDKEY, 0);
            keybd_event(keyCode, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
        }

Open in new window


But this time you save the KeyValue in the press event and send that to the PressKey function.

I think this should work for most keys anyway.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 8

Expert Comment

by:mac-will
ID: 35201009
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35202373
I'm not familiar but I will make myself now :)
Thank you for your help. I need a few final pushes if you don't mind.

But this time you save the KeyValue in the press event and send that to the PressKey function
There is no KeyValue property on the keypress event though... Did you mean KeyChar ?

Will it also recognize/replay ALT/SHIFT/CTR-key cobos ?

Thank you.
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35202606
        private void txtTrigger_KeyPress(object sender, KeyPressEventArgs e)
        {
          PressKey((byte)e.KeyChar);
         }

        [DllImport("user32.dll")]
        static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);

        void PressKey(byte keyCode)
        {
            const int KEYEVENTF_EXTENDEDKEY = 0x1;
            const int KEYEVENTF_KEYUP = 0x2;
            keybd_event(keyCode, 0x45, KEYEVENTF_EXTENDEDKEY, 0);
            keybd_event(keyCode, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
        }

Open in new window


^ Doesn't work. it returns some bizarre stuff. Pressing "P" triggers F1. Pressing "E" triggers number 4 ...etc.
0
 
LVL 8

Accepted Solution

by:
mac-will earned 500 total points
ID: 35207118
Sorry that should have been the KeyValue in the KeyDown/Up event...

Anyway, I notice on MSDN that keybd_event is deprecated so here is a little test form I wrote to show you a better way.

Simply copy all this code into a new file and run the form.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
    public partial class MacroExampleForm : Form
    {
        private TextBox txtReplayOutput;
        private Button btnRecord;
        private Button btnReplay;
        private Label lblStatus;
        private IContainer components = null;

        struct KeyboardEvent
        {
            public KeyboardEvent(byte _vKey, bool _keyUp)
            {
                vKey = _vKey;
                keyUp = _keyUp;
            }

            public byte vKey;
            public bool keyUp;
        }

        List<KeyboardEvent> recorded_data = new List<KeyboardEvent>();
        bool isRecording = false;

        public MacroExampleForm()
        {
            InitializeComponent();

            this.btnRecord.KeyDown += new KeyEventHandler(MacroExampleForm_KeyDown);
            this.btnRecord.KeyUp += new KeyEventHandler(MacroExampleForm_KeyUp);
        }

        void MacroExampleForm_KeyUp(object sender, KeyEventArgs e)
        {
            if (isRecording)
            {
                recorded_data.Add(new KeyboardEvent((byte)e.KeyValue, true));
            }
        }


        void MacroExampleForm_KeyDown(object sender, KeyEventArgs e)
        {
            if (isRecording)
            {
                recorded_data.Add(new KeyboardEvent((byte)e.KeyValue, false));
            }
        }

        private void btnRecord_Click(object sender, EventArgs e)
        {
            if (isRecording)
            {
                isRecording = false;
                lblStatus.Text = "Not Recording";
            }
            else
            {
                recorded_data.Clear();
                isRecording = true;
                lblStatus.Text = "Recording";
            }
        }

        private void btnReplay_Click(object sender, EventArgs e)
        {
            if (!isRecording && recorded_data.Count > 0)
            {
                foreach (KeyboardEvent keyE in recorded_data)
                {
                    txtReplayOutput.Select();

                    if (keyE.keyUp)
                    {
                        KeyboardSimulator.SendKeyUp(keyE.vKey);
                    }
                    else
                    {
                        KeyboardSimulator.SendKeyDown(keyE.vKey);
                    }
                }
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.txtReplayOutput = new System.Windows.Forms.TextBox();
            this.btnRecord = new System.Windows.Forms.Button();
            this.btnReplay = new System.Windows.Forms.Button();
            this.lblStatus = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // txtReplayOutput
            // 
            this.txtReplayOutput.Location = new System.Drawing.Point(12, 50);
            this.txtReplayOutput.Multiline = true;
            this.txtReplayOutput.Name = "txtReplayOutput";
            this.txtReplayOutput.Size = new System.Drawing.Size(296, 145);
            this.txtReplayOutput.TabIndex = 0;
            // 
            // btnRecord
            // 
            this.btnRecord.Location = new System.Drawing.Point(108, 201);
            this.btnRecord.Name = "btnRecord";
            this.btnRecord.Size = new System.Drawing.Size(97, 49);
            this.btnRecord.TabIndex = 1;
            this.btnRecord.Text = "Record";
            this.btnRecord.UseVisualStyleBackColor = true;
            this.btnRecord.Click += new System.EventHandler(this.btnRecord_Click);
            // 
            // btnReplay
            // 
            this.btnReplay.Location = new System.Drawing.Point(211, 201);
            this.btnReplay.Name = "btnReplay";
            this.btnReplay.Size = new System.Drawing.Size(97, 49);
            this.btnReplay.TabIndex = 2;
            this.btnReplay.Text = "Replay";
            this.btnReplay.UseVisualStyleBackColor = true;
            this.btnReplay.Click += new System.EventHandler(this.btnReplay_Click);
            // 
            // lblStatus
            // 
            this.lblStatus.AutoSize = true;
            this.lblStatus.Location = new System.Drawing.Point(12, 24);
            this.lblStatus.Name = "lblStatus";
            this.lblStatus.Size = new System.Drawing.Size(76, 13);
            this.lblStatus.TabIndex = 3;
            this.lblStatus.Text = "Not Recording";
            // 
            // MacroExampleForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(322, 264);
            this.Controls.Add(this.lblStatus);
            this.Controls.Add(this.btnReplay);
            this.Controls.Add(this.btnRecord);
            this.Controls.Add(this.txtReplayOutput);
            this.Name = "MacroExampleForm";
            this.Text = "MacroExampleForm";
            this.ResumeLayout(false);
            this.PerformLayout();

        }
    }


    public static class KeyboardSimulator
    {

        public static void SendKeyDown(byte vKey)
        {
            INPUT[] inputs = new INPUT[1];
            inputs[0].type = INPUT_KEYBOARD;
            inputs[0].ki.wVk = (ushort)vKey;

            uint rval = SendInput(1, ref inputs[0], Marshal.SizeOf(new INPUT()));
            if (rval != 1)
            {
                //TODO - make this more descriptive...
                throw new Exception();
            }
        }

        public static void SendKeyUp(byte vKey)
        {
            INPUT input = new INPUT();
            input.type = INPUT_KEYBOARD;
            input.ki.wVk = (ushort)vKey;
            input.ki.dwFlags = KEYEVENTF_KEYUP;

            uint rval = SendInput(1, ref input, Marshal.SizeOf(new INPUT()));
            if (rval != 1)
            {
                //TODO - make this more descriptive...
                throw new Exception();
            }
        }

        [DllImport("user32.dll", SetLastError = true)]
        static extern uint SendInput(uint nInputs, ref INPUT pInputs, int cbSize);

        public const int INPUT_KEYBOARD = 1;
        public const uint KEYEVENTF_EXTENDEDKEY = 0x0001;
        public const uint KEYEVENTF_KEYUP = 0x0002;
        public const uint KEYEVENTF_UNICODE = 0x0004;
        public const uint KEYEVENTF_SCANCODE = 0x0008;
    }


    [StructLayout(LayoutKind.Sequential)]
    public struct KEYBDINPUT
    {
        public ushort wVk;
        public ushort wScan;
        public uint dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }


    [StructLayout(LayoutKind.Explicit, Size = 28)]
    public struct INPUT
    {
        [FieldOffset(0)]
        public uint type;
        [FieldOffset(4)]
        public KEYBDINPUT ki;
    };
}

Open in new window



Good luck

mac
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35242209
Sorry for a delay but I'm still having problems with this that I can't seem to figure out. Everything seems right yet....

So what I'm storing in my XML file are e.KeyValue values returned from a control's key down event. And then use them to simulate key presses. So basically it's this:

        private void txTrigger_KeyDown(object sender, KeyEventArgs e)
        {

            KeyboardSimulator.SendKeyDown((byte)e.KeyValue);
        }

        private void txtTrigger_KeyUp(object sender, KeyEventArgs e)
        {
            KeyboardSimulator.SendKeyUp((byte)e.KeyValue);
        }

Open in new window


Rest of the code is what you've posted above. I didn't make any changes to the KeybardSimulator class at all. Using it exactly as you posted.

Yet what happens is when txtTrigger key click is triggered it gets stuck in unlimited KeyDown loop. So if I press "K" for example I get KKKKKKKKKKKKKKKKKKKKKKKKKKKK...etc.

I can't figure out why and what's different in my code from what you wrote.
0
 
LVL 8

Expert Comment

by:mac-will
ID: 35257934
Hi,

If you are sending a keydown event from the keydown handler you will of course get a loop.

Basically you are saying when 'K' is pressed press it again.

Is this a macro recorder of some sort?

Basically you need to record the various key down and key up events than save this sequence to your xml file and replay the sequence when you need to.

Try getting the example I posted to work without any modifications I think it will explain a lot.

btw the reason for saving both the key down and up events is for modifier keys.

for example this sequence:

shift down
k down
k up
shift up

will yield 'K'
if instead you just recorded key presses (down - up event) for the same sequence you would have recorded k press than shift press which would yield 'k' and not 'K' as intended.
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35386360
Account expired at the worst time.
This is not abandoned.
0
 
LVL 12

Expert Comment

by:Mohamed Abowarda
ID: 35386399
@techsuppoprt: You need to interact with the experts to solve the question, you have left the question since the last expert comment at 03/30/11 therefore the question was classified as abandoned.
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35387998
Mac, thanks for your help.
I'm about to close this one.
I have one last question i you don't mind.

My key strokes are being forwarded/replayed back to the interface just fine , lets say if I use notepad.
But then I have some programs where it doesn't work.

What could be blocking keys in an application from being sent to its interface ? Any clue ?
0
 
LVL 8

Expert Comment

by:mac-will
ID: 35460106
I would guess it might have something to do with the control that has focus?  Perhaps these application do not propagate key presses as normal to child controls?

Just a guess.
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35461893
Alright. Well thank you :)
0
 
LVL 1

Author Comment

by:techsuppoprt
ID: 35469705
0

Featured Post

Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

Join & Write a Comment

Suggested Solutions

How to install Selenium IDE and loops for quick automated testing. Get Selenium IDE from http://seleniumhq.org (http://seleniumhq.org) Go to that link and select download selenium in the right hand columnThat will then direct you to their downlo…
Entity Framework is a powerful tool to help you interact with the DataBase but still doesn't help much when we have a Stored Procedure that returns more than one resultset. The solution takes some of out-of-the-box thinking; read on!
The viewer will learn how to use NetBeans IDE 8.0 for Windows to connect to a MySQL database. Open Services Panel: Create a new connection using New Connection Wizard: Create a test database called eetutorial: Create a new test tabel called ee…
The viewer will learn how to use and create keystrokes in Netbeans IDE 8.0 for Windows.

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now