0

I have this Winforms code (at the bottom). When I run it, a form appears with "button1". I click the button, it disappears, then I press a key - which should trigger OnKeyDown but nothing happens. I add a breakpoint on MessageBox::Show(...) line, click a key - now the breakpoint is hit, the MessageBox is displayed and every subsequent key press will also show the MessageBox, even if I remove the breakpoint.

Notes:

  • I have tested this on 2 computers running Visual Studio 2013, the project is a .Net 4.5 Winforms project.
  • The breakpoint must be set in Visual Studio after starting to debug the project.
  • Running the project without debugging never shows the MessageBox
  • Per Sriram's note, setting this.KeyPreview = true in the constructor causes the MessageBox to always be displayed. Though I still don't understand why setting a breakpoint will cause the MessageBox to be displayed for that debugging session.

public partial class Form1 : Form
{
    public Form1()
    {
        button1 = new Button();
        button1.Location = new Point(197, 13);
        button1.Name = "button1";
        button1.Text = "button1";
        button1.Click += button1_Click;
        Controls.Add(button1);
        Name = "TestForm";
        KeyDown += OnKeyDown;
    }

    private static void OnKeyDown(object sender, KeyEventArgs keyEventArgs)
    {
        MessageBox.Show("This is shown only after a breakpoint is set on this line");
    }

    private void button1_Click(object sender, EventArgs e)
    {
        button1.Visible = false;
    }

    private readonly Button button1;
}
ytoledano
  • 3,003
  • 2
  • 24
  • 39

3 Answers3

2

It doesn't have anything to do with setting a breakpoint. This behavior is induced by the other thing that happens when the breakpoint hits: the window is de-activated. An inevitable side-effect of running the debugger on the same machine as your app. When you resume running, your window gets re-activated. Now you see the side-effect of compatibility code inside Winforms that makes the UI behave the same way as its predecessor (VB6), it goes looking more aggressively for some control to take the focus.

Note that you get the exact same behavior when you press Alt+Tab to switch to another window. Alt+Tab again to switch back.

Explaining this is a fairly long story, I'll try to keep it as condense as possible. Focus is a big deal in a GUI, keystrokes are sent to the window that currently has the focus. There can be only one window in a thread that owns it. Or none. Such a window should be the kind of window that was designed to handle focus. It should indicate it, typically with a focus rectangle or a caret, sometimes a distinct color. And of course do something meaningful with key strokes. Surely you know them, Button, TextBox, ListBox, etcetera.

The Form class is not one of them. It is a container control, a home for controls that can receive the focus. It is very, very reluctant to take the focus. Re-inforced by its control styles, it has ControlStyles.Selectable turned off, ControlStyles.ContainerControl turned on. Other examples of container controls are Panel, UserControl, ToolStrip, GroupBox.

So when you set the button's Visible property to false then Winforms needs to find another control to give the focus to. There are none left, it gives up and there is no focus window anymore. When you re-activate the window then the VB6 compatibility kicks in and the Form does get the focus.

There is only ever one reason why a container control should be interested in key strokes, it should use them to implement shortcut keystrokes. Like Alt+F4 to close a window, F1 to show help, Alt+F to activate the File menu, etcetera. Keystrokes that should work regardless of which control has the focus. Beware that the common advice you'd get is to use the form's KeyPreview property, that is however another troublesome VB6 compatibility property. It doesn't work for all keystrokes and is specific to a Form.

The proper way is to override the ProcessCmdKey() method.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
1

The issue is that when you click the button it gains and retains focus, even after you have hidden it in code. Leaving to place a breakpoint and returning to your application is deceptive as the key step is really just to return focus to the form (so that its KeyDown handler fires). You can make this work simply by switching to any other application and then returning to your form.

Setting KeyPreview to true means that the form gets to "peek" at events that are destined for other windowed controls (like buttons). When the button has focus, the form otherwise does not see the keydown event - it is directed to the button instead.

One of the big reasons to move to the more modern WPF framework is that it has much better handling for events like this when you have a deep and complex component hierarchy. Centralizing handlers becomes much easier.

J...
  • 30,968
  • 6
  • 66
  • 143
  • Indeed, even alt-tab to any process, then alt-tab back to the form causes the MessageBox to be displayed. – ytoledano Aug 30 '14 at 13:20
0

This work may help you

protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
        //your code
        return base.ProcessCmdKey(ref msg, keyData);
    }
Hamix
  • 1,323
  • 7
  • 18