WinForms - Accessing Mouse And Keyboard State

Skill

WinForms - Accessing Mouse And Keyboard State

Posted in:

Generally, Windows programs are very event based systems - a user presses a key, the key down event is fired with the information on what key was pressed, and the application reacts accordingly. There are sometimes cases, however, when the program needs to know the current state of the mouse/keyboard, but we don't have a handy event with the info about which keys are down or mouse buttons are pressed. Now you could always maintain this state yourself in some sort of data structure and updating it on key/mouse up and down events - but that is a lot of work, and, as it turns out, unnecessary. There are already functions built into .NET that let you access most (but not all) of this info - and in the case where there is not a built in function, the answer is just a simple interop away! And that is what we are going to take a look at today.

Below we have a screen shot of the little app we are going to build today. All it does is print out the current state of the mouse and keyboard when the button is clicked. Not a very useful app by any means, but what is useful is the code that populates those visible fields. By looking at the screen shot, you can probably tell what we are going to look at today: getting the current mouse position, the current mouse buttons held down, the current modifier keys pressed (ctrl, alt, shift), the current keys pressed, and the current keys toggled (useful for numlock, capslock, etc).

Screenshot Of Input State App

Ok, so now lets walk through how we get this information. The first three are easy - there are ways built into .NET to get the info. First we have mouse position:

_MousePosLabel.Text = Control.MousePosition.X + ", " + Control.MousePosition.Y;

That's right - the current position of the mouse is always available in the static property MousePosition off of Control. The only special thing to note here is that the value is in screen coordinates, so you will probably need to use a PointToClient call to get it into useful coordinates for your application. Next we have mouse buttons:

_MouseButtonsLabel.Text = Control.MouseButtons.ToString();

Again, available off Control as a static property. This returns an instance of the MouseButtons enum containing the currently pressed mouse buttons. Checking this enum to see if a specific mouse button is pressed takes a little bit of boolean logic, but it isn't that bad. For instance, to check and see if the left mouse button is pressed, you would do something like this:

if((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
{
  //Do stuff here
}

Next we have another easy one - modifier keys:

_ModifierKeysLabel.Text = Control.ModifierKeys.ToString();

Yet again, it is a static property off of Control. Don't worry, this is the last easy one. Here, the property returns an instance of the Keys enum. Even though it is an instance of the full blow keys enum (i.e., it has entries for every key), only Keys.Control, Keys.Shift, or Keys.Alt will ever be set (since this only returns modifier keys). Once again, like the mouse buttons, you need to do a bit of logic to see if particular modifier keys are pressed (since more than one can be pressed at once):

//Checks to see that Control is pressed, but doesn't care
// about other modifier keys
if((Control.ModifierKeys & Keys.Control) == Keys.Control)
{
  //Do stuff here
}

//Checks to see Control is pressed and
//Alt and Shift are not pressed
if(Control.ModifierKeys == Keys.Control)
{
  //Do stuff here
}

//Checks to see that Control and Shift are pressed,
//but not Alt
if(Control.ModifierKeys == (Keys.Control | Keys.Shift))
{
  //Do stuff here
}

Ok, now we are on to the hard items - checking the down/toggle state of any key. Sadly, .NET does not expose this, even though it is a standard Win32 function. It is called GetKeyState. Below you can see a small class that wraps it up nicely:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace MouseKeyboardStateTest
{
  public abstract class Keyboard
  {
    [Flags]
    private enum KeyStates
    {
      None = 0,
      Down = 1,
      Toggled = 2
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern short GetKeyState(int keyCode);

    private static KeyStates GetKeyState(Keys key)
    {
      KeyStates state = KeyStates.None;

      short retVal = GetKeyState((int)key);

      //If the high-order bit is 1, the key is down
      //otherwise, it is up.
      if ((retVal & 0x8000) == 0x8000)
        state |= KeyStates.Down;

      //If the low-order bit is 1, the key is toggled.
      if ((retVal & 1) == 1)
        state |= KeyStates.Toggled;

      return state;
    }

    public static bool IsKeyDown(Keys key)
    {
      return KeyStates.Down == (GetKeyState(key) & KeyStates.Down);
    }

    public static bool IsKeyToggled(Keys key)
    {
      return KeyStates.Toggled == (GetKeyState(key) & KeyStates.Toggled);
    }
  }
}

The important part of this code is the GetKeyState function (the one with code, not the interoped one). Essentially, we take a Keys enum and cast it as an int, and pass it to the Win32 GetKeyState function. Fortunately, the int cast of the Keys enum does exactly what we want - it gives us the value that the underlying API needs (otherwise we would have to some sort of tedious mapping).

Now, the value that comes back is a little odd. It is a short (so 16 bits) - but all but two of those bytes don't matter. We care about the high bit and the low bit. If the high bit is 1, then the key is currently down. If the low bit is 1, then the key is currently toggled. Granted, toggling doesn't really make sense for things other than caps lock, num lock, or scroll lock, but Windows keeps track of a toggled state for all keys. So with those two values, we compile a KeyState enum that we hand back.

That KeyState enum is used by two functions here - the IsKeyDown and IsKeyToggled function. All they do is take a look at that enum and return true or false as appropriate.

So how do we use this in actual code? Well, its pretty simple. Heres the code for the Down and Toggled fields in the little app shown above:

StringBuilder text = new StringBuilder();
foreach (Keys k in Enum.GetValues(typeof(Keys)))
{
  if (Keyboard.IsKeyDown(k))
  {
    if (text.Length != 0)
      text.Append(", ");

    text.Append(k);
  }
}
_KeysPressedLabel.Text = text.ToString();

text = new StringBuilder();
foreach (Keys k in Enum.GetValues(typeof(Keys)))
{
  if (Keyboard.IsKeyToggled(k))
  {
    if (text.Length != 0)
      text.Append(", ");

    text.Append(k);
  }
}
_KeysToggledLabel.Text = text.ToString();

Pretty much, we just check IsKeyDown and IsKeyToggled for every key in the Keys enum, and compile a string (using a string builder) with the result. Granted, the loops are kind of annoying, but its rare that you would have to know the state of every key at once, right? Anyway, even if you did, there is a different Win32 function you could pull in (which we aren't going to talk about here) called GetKeyboardState that does exactly that.

So here is all the code that executes when the "Check Now" button is pressed:

private void CheckBtn_Click(object sender, EventArgs e)
{
  _MousePosLabel.Text = Control.MousePosition.X + ", " + Control.MousePosition.Y;
     
  _MouseButtonsLabel.Text = Control.MouseButtons.ToString();

  _ModifierKeysLabel.Text = Control.ModifierKeys.ToString();

  StringBuilder text = new StringBuilder();
  foreach (Keys k in Enum.GetValues(typeof(Keys)))
  {
    if (Keyboard.IsKeyDown(k))
    {
      if (text.Length != 0)
        text.Append(", ");

      text.Append(k);
    }
  }
  _KeysPressedLabel.Text = text.ToString();

  text = new StringBuilder();
  foreach (Keys k in Enum.GetValues(typeof(Keys)))
  {
    if (Keyboard.IsKeyToggled(k))
    {
      if (text.Length != 0)
        text.Append(", ");

      text.Append(k);
    }
  }
  _KeysToggledLabel.Text = text.ToString();
}

And that is it for today! You can download the Visual Studio project with all this code here, and, as usual, if you have any questions or comments feel free to leave them below.

AnimalEnthusiast
04/02/2008 - 13:19

This is a great article. It is very interesting and informative!

reply

Nemon
06/12/2008 - 23:54

Thanks for the article. It helped me a lot.

reply

Desotocoder
03/06/2009 - 17:09

I have just stumbled upon this website...your tutorials are great!

reply

Mohammad
07/07/2009 - 01:12

excellent article

reply

Luiz Freneda
08/03/2009 - 15:00

Thanks ;-)

reply

Anonymous
12/07/2009 - 22:44

Thanks to whoever's page this is.

Chris

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.

Sponsors