Keyboard events in WPF

A keyboard event is generated whenever the user presses a key or a combination of keys on the keyboard. In order to direct a keyboard event to a particular element in the interface, that element must have the focus. As such, by default, only interface elements that can take the focus can receive keyboard events, so geometric shapes such as Rectangle and Ellipse, and layouts such as the Canvas do not take the focus (although they can be made to; see later).

The main keyboard events that we wish to handle are KeyUp and KeyDown. The easiest way to see how this is done is by considering an example. We’ll write a little program that displays a blank Canvas and allows the user to add little coloured squares to the Canvas by pressing certain keys. To begin, we’ll draw a filled square in either red, green or blue depending on whether the R, G or B key is pressed. All other keys do nothing.

Create a new WPF project in Visual Studio (you can use Expression Blend if you like, but we’ll be doing all the coding in VS). Change the default LayoutRoot to a Canvas (it’s created in VS as a Grid), and call it CanvasRoot. Add a handler for the KeyDown event to the Canvas, calling it CanvasRoot_KeyDown.

You’ll note that the second argument in this handler is an object of class KeyEventArgs. This object contains all the information you need to know about which key was pressed. In particular, the key can be found from the Key property, so we can set the colour of the square to be drawn like this:

      switch (e.Key)
      {
        case Key.R:
          squareColor = Colors.Red;
          break;
        case Key.G:
          squareColor = Colors.Green;
          break;
        case Key.B:
          squareColor = Colors.Blue;
          break;
        default:
          return;
      }

Each key has its own entry in the Key enum, and we’ve used the Colors enum to specify the colours by name, although we could also have used RGB values.

We can write the first version of a drawSquare() method that draws a square at a random position on the Canvas. We have:

    Random ranNum = new Random();
    int squareSize = 10;

    private void drawSquare(Color squareColor)
    {
      Rectangle square = new Rectangle();
      square.Fill = new SolidColorBrush(squareColor);
      square.Width = squareSize;
      square.Height = squareSize;
      int squareX = ranNum.Next((int)CanvasRoot.ActualWidth - squareSize);
      int squareY = ranNum.Next((int)CanvasRoot.ActualHeight - squareSize);
      Canvas.SetLeft(square, squareX);
      Canvas.SetTop(square, squareY);
      CanvasRoot.Children.Add(square);
    }

We’ve created a Random object on line 1 so we can generate random numbers. We’ve set the square size to 10 on line 2.

The rest of the method should be fairly self-explanatory, but note that in order to ensure that the entire square is drawn within the boundaries of the Canvas, we use the ActualWidth and ActualHeight values of the Canvas, since the Width and Height properties are not defined as numbers if the Canvas’s dimensions are set to ‘Auto’. We subtract off the squareSize to ensure the square fits inside the Canvas. The last three lines of the method set the square’s position and add it as a child of the Canvas.

If you run the program as it is, you’ll probably find that pressing R, G or B has no effect. This is because, as we mentioned above, the Canvas by default does not take the focus, so even clicking in it with the mouse won’t help. There are two solutions to this problem. Perhaps the simplest is to move the event handler from the Canvas to the underlying Window, and if you do that, you’ll find the program now works. However, this isn’t the best solution, since in a more general application, the Canvas might be just one component of a larger interface, and we want  the Canvas to have the focus only some of the time.

The other solution is to make the Canvas focusable and give it the focus. This is easily done by adding a couple of lines to the MainWindow() constructor:

    public MainWindow()
    {
      InitializeComponent();
      CanvasRoot.Focusable = true;
      CanvasRoot.Focus();
    }

The only drawback to this is that the Canvas now has a dotted line drawn around it. There are ways to fix this too, but we’ll leave it for now.

That’s about all there is to keyboard events if all you want to do is detect when a single key has been pressed or released. However, often we want to press a modifier key such as Shift or Control to give a different effect. We’ll illustrate this here by modifying the program so that if the Shift key is pressed along with R, G or B, the square is drawn in outline rather than filled. Furthermore, if we press the Shift and Control keys together, we get a filled magenta square no matter which of R, G or B is pressed. To implement this, we can add the following to the CanvasRoot_KeyDown handler:

      squareStyle style;
      if (Keyboard.Modifiers == ModifierKeys.Shift)
      {
        style = squareStyle.Outline;
      }
      else if (Keyboard.Modifiers == (ModifierKeys.Shift | ModifierKeys.Control))
      {
        squareColor = Colors.Magenta;
        style = squareStyle.Fill;
      }
      else
      {
        style = squareStyle.Fill;
      }

We’ve added an enum definition at the top of the class to handle the different styles of square:

    enum squareStyle { Fill, Outline };

If we have only a single modifier key, we can test for this using a simple == comparison as on line 2. The Keyboard.Modifiers value contains a code giving all modifier keys that have been pressed, where each bit in the code corresponds to a different modifier. If a particular modifier such as Shift has been pressed, its corresonding bit will be 1 with all other bits set to 0.

If we have more than one modifier pressed, such as Shift + Control, then both their bits will be 1, so we can compare Keyboard.Modifiers to the bitwise-or of ModifierKeys.Shift and ModifierKeys.Control.

We now modify the drawSquare() method to cope with different styles as well as colours:

    private void drawSquare(Color squareColor, squareStyle style)
    {
      Rectangle square = new Rectangle();
      switch (style)
      {
        case squareStyle.Outline:
          square.Stroke = new SolidColorBrush(squareColor);
          break;
        default:
          square.Fill = new SolidColorBrush(squareColor);
          break;
      }
      square.Width = squareSize;
      square.Height = squareSize;
      int squareX = ranNum.Next((int)CanvasRoot.ActualWidth - squareSize);
      int squareY = ranNum.Next((int)CanvasRoot.ActualHeight - squareSize);
      Canvas.SetLeft(square, squareX);
      Canvas.SetTop(square, squareY);
      CanvasRoot.Children.Add(square);
    }

We see that we set Stroke for outlines and Fill for fills.

There is one fly in the ointment however. The Alt modifer behaves in a strange way and must be handled separately. If Alt is combined with another modifier, as in Shift+Alt, then it can be handled in the same way as above. However, if you try to use Alt on its own as a modifer, the above code won’t work. This is because, in Windows, Alt is used as a System key in such operations as Alt+Tab for switching between windows. If Alt is pressed when you press any other key, the e.Key field will always come up as Key.System. In order to access the underlying key that was pressed along with Alt, you need to access the SystemKey field of the KeyEventArgs object. For example, if we want to have Alt+Y produce a yellow (goldenrod, actually) filled square, we could write within the key down handler:

      if ((Keyboard.Modifiers == ModifierKeys.Alt) && (e.SystemKey == Key.Y))
      {
        squareColor = Colors.Goldenrod;
        drawSquare(squareColor, squareStyle.Fill);
        return;
      }

With this final addition, the complete handler looks like this:

    private void CanvasRoot_KeyDown(object sender, KeyEventArgs e)
    {
      Color squareColor;

      // Alt key is treated differently. e.Key == System when Alt is pressed;
      // Actual key is stored in e.SystemKey
      if ((Keyboard.Modifiers == ModifierKeys.Alt) && (e.SystemKey == Key.Y))
      {
        squareColor = Colors.Goldenrod;
        drawSquare(squareColor, squareStyle.Fill);
        return;
      }

      switch (e.Key)
      {
        case Key.R:
          squareColor = Colors.Red;
          break;
        case Key.G:
          squareColor = Colors.Green;
          break;
        case Key.B:
          squareColor = Colors.Blue;
          break;
        default:
          return;
      }

      squareStyle style;
      if (Keyboard.Modifiers == ModifierKeys.Shift)
      {
        style = squareStyle.Outline;
      }
      else if (Keyboard.Modifiers == (ModifierKeys.Shift | ModifierKeys.Control))
      {
        squareColor = Colors.Magenta;
        style = squareStyle.Fill;
      }
      else
      {
        style = squareStyle.Fill;
      }

      drawSquare(squareColor, style);
    }

The complete code for this post is available here.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: