A new way of saying IsInputKey: PreviewKeyDown

A year ago, I diagramed the maze that is Windows Forms keyboard handling.

 

There are some new helper methods in 2.0 that I havent gotten a chance to mention, which I thought might be interesting. This first is PreviewKeyDown.

If you've struggled before with having to override a class in order to set IsInputKey=true so you can get arrow keys for your control, you can now also use the PreviewKeyDown event. 

The Button Sample

Lets take the example where you're building a button that shows a menu when the down or up arrow is pressed. Typically the button will not get these keys as they're "navigational", and the button doesn't get called back on KeyDown. By handling the PreviewKeyDown event and setting IsInputKey = true, the button will now recieve the Down and Up arrows. Note that these two keys will no longer move focus to the next control as they're no longer "navigational."

namespace WindowsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
// this form just has one button on it.
button1.PreviewKeyDown +=new PreviewKeyDownEventHandler(button1_PreviewKeyDown);
button1.KeyDown += new KeyEventHandler(button1_KeyDown);
button1.ContextMenuStrip = new ContextMenuStrip();
button1.ContextMenuStrip.Items.Add("One");
button1.ContextMenuStrip.Items.Add("Two");
button1.ContextMenuStrip.Items.Add("Three");
}
// Because the arrow keys are normally not input keys to
// a button, (they normally change selection to the adjacent control)
// KeyDown is not typically fired for a down arrow. In
// 1.1 of the framework you could override the Button and use IsInputKey
// in 2.0 you can additionally respond to the PreviewKeyDown event.
void button1_KeyDown(object sender, KeyEventArgs e) {
switch (e.KeyCode) {
case Keys.Down:
case Keys.Up:
if (button1.ContextMenuStrip != null) {
button1.ContextMenuStrip.Show(button1, new Point(0, button1.Height), ToolStripDropDownDirection.BelowRight);
}
break;
}
}

// PreviewKeyDown is where you preview the key to see if you want it
// do not put any logic here other than to say you want the key
// instead use the KeyDown event after setting IsInputKey to true
private void button1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) {
switch (e.KeyCode) {
case Keys.Down:
case Keys.Up:
e.IsInputKey = true;
break;
}
}
}
}

Hmm, but why doesn't this work for panel?

There's really two pieces to the puzzle: whether a control can accept certain keys, and whether or not a control can accept focus. A Panel, because it derives from ScrollableControl is not focusable by default. This behavior is controlled by the flag ControlStyles.Selectable - and you need to additionally call SetStyle(ControlStyles.Selectable, true). Since this API is protected, you will need to inherit. Finally, to say that the Panel participates in the tab order, you need to set TabStop = true. 

public class MyPanelThatAcceptsArrowKeys : Panel {
public MyPanelThatAcceptsArrowKeys() {
// specifies whether or not it can get focus
SetStyle(ControlStyles.Selectable, true);
// specifies whether it should participate in tab order
this.TabStop = true;

                ChangeBackColor();
}
protected override bool IsInputKey(Keys keyData) {
switch (keyData) {
case Keys.Up:
case Keys.Down:
return true;
}
return base.IsInputKey(keyData);
}
protected override void OnKeyDown(KeyEventArgs e) {
switch (e.KeyCode) {
case Keys.Up:
case Keys.Down:
ChangeBackColor();
break;
}
base.OnKeyDown(e);
}
private void ChangeBackColor() {
Random rand = new Random();
this.BackColor = Color.FromArgb(rand.Next(0, 255), rand.Next(0, 255), rand.Next(0, 255));
            }
}