It is hard to describe the issue. Let me illustrate it with a very simple example.
Start from a new solution of winform(.net framework 4.8).
Add a menustrip with a textbox, then a datagridview.
And let's handle KeyDown event of datagridview.
if (e.KeyCode == Keys.F3)
toolStripTextBox1.Focus();
Ok, now we start the program.
Click the datagridview to focus on it.
Click the textbox to change focus.
Press Esc on your keyboard.
You can see that the datagridview gets the focus as expected.
But if you make a little change in step two, the result will be confusing. Press F3 instead of clicking the textbox. When you press Esc this time, the focus is just lost. I tried to print the name and the handle of focused control. It turned out to be the textbox itself. Can somebody explain it?
Now I'm quite sure it is some kind of bug. After Jimi's mention of hwndThatLostFocus, I had traced this variable for several hours. Although I failed to locate the exact position where hwndThatLostFocus was changed, something unreasonable was found: when Focus() was called in different cases, the result lost consistency.
Under most circumstances, if Focus() of ToolStripTextBox is called, hwndThatLostFocus of ToolStripTextBox will be set to 0, just like the example in my question. But if you click the datagridview and click the ToolStripTextBox and then click the datagridview agian, this time you call Focus(), hwndThatLostFocus will remain a pointer to the datagridview. In addition, this could be reproduced in .net 6.
Later I will report this to Microsoft. For now, there are three ways to avoid this.
Simulate a mouse click by SetCursorPos and mouse_event in user32.dll.
Use reflection like Jimi's advice.
Override ProcessCmdKey in Form, and take care of Keys.Escape yourself:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Escape && msg.HWnd == toolStripTextBox1.TextBox.Handle)
{
dataGridView1.Focus();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
As I avoid using unnecessary reflect, and Dllimport is also some kind of ugly to me, I prefer the third one.
Related
I'm new here, and I may be blasted for not seeing the old posts about Keys, but I assure you I have read many of them and cannot find the answer I am looking for.
I have a C# program, a calculator, that correctly calculates equations, but I want to be able to call methods by both clicking and keyboard input. Like so if user types in 2 + 2 ENTER the textbox will show 4. The only way the program does that at the moment is if the user actually clicks those buttons. Researching how Keys work in C# I found a lot of information about KeyCode and Keys, but very little information about how to actually implement them within the program. One thing that I came across concerning implementing the code was this:
private void Calculator_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up)
{
MessageBox.Show("Up button was pressed");
}
}
The name of the form is Calculator. I can compile this code without a problem, but it doesn't seem to do anything. I can place the entire program for you guys if you want, but it is several pages long and has a lot of comments and other stuff. I just don't understand why I can't make my program read a key. I also tried KeyPress instead of KeyDown, still nothing. Any help would be much appreciated.
Edit:
Marcel N. gave a good answer and I was able to get it to work after enabling the KeyPreview and using the code:
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.D1)
cmd1.PerformClick();
if (e.KeyCode == Keys.D2)
cmd2.PerformClick();
if (e.KeyCode == Keys.D3)
cmd3.PerformClick();
if (e.KeyCode == Keys.D4)
cmd4.PerformClick();
......................
}
I am very happy that all the numbers work, but am still having issues getting Enter, Divide, Multiply, and the arrow keys to work. Thanks for the speedy help.
You need to set the KeyPreview property on the form to true.
This will cause all key events to be passed to the form first, before they are delegated to the focused control. Then, you can use your current KeyDown handler to call your methods/handlers.
Finally, if you don't want the key events to reach the focused control at all then make sure to set the KeyPressEventArgs.Handled property to true when you receive the event at the form level.
I tried creating a custom CheckedListBox and overriding the OnKeyUp, OnKeyPress, OnKeyDown, OnPreviewKeyDown methods but I couldn't accomplish what I wanted to accomplish.
Essentially what is happening now is that if the user presses a key while the CheckedListBox has focus, the selected item changes. For example, if the user presses the up or down key, the next/previous item in the CheckedListBox is selected. if the user presses 'A', then the first item that begins with the letter 'A' is selected.
I want the CheckedListBox to behave in such a way that it is completely unresponsive to key presses (but still responsive to mouse clicks).
Can someone please tell me how to accomplish this?
I think it is possible to achieve this by inheriting from CheckedListBox and overriding it's ProcessCmdKey() method to return true. That should ignore all commands.
I'll give it a try in a solution and report back :)
Update:
It works.
Code I used:
using System.Windows.Forms;
public class CheckedListBoxIgnoreKeyboard : CheckedListBox
{
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
return true;
}
}
A rebuild is required for the control to show up in Toolbox.
did you try to use the KeyDown event with this?
e.SuppressKeyPress = true;
seem, on my side to work
Basically, I have a form with a custom control on it (and nothing else). The custom control is completely empty, and the form has KeyPreview set to true.
With this setup, I am not receiving any KeyDown events for any arrow keys or Tab. Every other key that I have on my keyboard works. I have KeyDown event handlers hooked up to everything that has such events, so I'm sure I'm not missing anything.
Also of note is that if I remove the (completely empty) custom control, I DO get the arrow key events.
What on earth is going on here?
EDIT:
I added this to both the form and the control, but I'm STILL not getting arrow keys:
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case 0x100: //WM_KEYDOWN
//this is the control's version. In the form, it's this.Text
ParentForm.Text = ((Keys)m.WParam).ToString();
break;
}
base.WndProc(ref m);
}
I also checked with Spy++, and determined that the form itself is not getting any WM_KEYDOWN messages, they're all going to the control. However, that said, the control IS getting the arrow key WM_KEYDOWN messages. Sigh.
Edit 2: I've also updated the ZIP file with this version. Please look at it, if you want to help...
Edit 3:
I've figured this out, sort of. The form is eating the arrow keys, probably in an attempt to maintain focus amongst its children. This is proven by the fact that I DO get the events if the form is empty.
Anyway, if I add this code to the form, I start getting the events again:
public override bool PreProcessMessage(ref Message msg) {
switch (msg.Msg) {
case 0x100: //WM_KEYDOWN
return false;
}
return base.PreProcessMessage(ref msg);
}
When I override this, the form doesn't get a chance to do its dirty work, and so I get my KeyDown events as I expect. I assume that a side effect of this is that I can no longer use my keyboard to navigate the form (not a big deal in this case, as it's a game, and the entire purpose of this exercise is to implement keyboard navigation!)
The question still remains about how to disable this "properly", if there is a way...
I've done some extensive testing, and I've figured everything out. I wrote a blog post detailing the solution.
In short, you want to override the ProcessDialogKey method in the form:
protected override bool ProcessDialogKey(Keys keyData) {
return false;
}
This will cause the arrow keys (and tab) to be delivered as normal KeyDown events. HOWEVER! This will also cause the normal dialogue key functionality (using Tab to navigate controls, etc) to fail. If you want to retain that, but still get the KeyDown event, use this instead:
protected override bool ProcessDialogKey(Keys keyData) {
OnKeyDown(new KeyEventArgs(keyData));
return base.ProcessDialogKey(keyData);
}
This will deliver a KeyDown message, while still doing normal dialogue navigation.
If focus is your issue, and you can't get your user control to take a focus and keep it, a simple work-around solution would be to echo the event to your user control on the key event you are concerned about. Subscribe your forms keydown or keypress events and then have that event raise an event to your user control.
So essentially, Form1_KeyPress would Call UserControl1_KeyPress with the sender and event args from Form1_KeyPress e.g.
protected void Form1_KeyPress(object sender, KeyEventArgs e)
{
UserControl1_KeyPress(sender, e);
}
Otherwise, you may have to take the long route and override your WndProc events to get the functionality you desire.
I've been working for a while on my Windows Forms project, and I decided to experiment with keyboard shortcuts. After a bit of reading, I figured I had to just write an event handler and bind it to the form's KeyDown event:
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.Alt && e.KeyCode == Keys.O)
{
MessageBox.Show("Ctrl+Alt+O: magic!");
}
}
I did that the good ol' way of opening the Properties panel of the Visual Studio designer, then double-clicking on the KeyDown event of my form to generate the Form1_KeyDown event handler. But on testing my application, the form doesn't respond at all to the Ctrl+Alt+O keyboard shortcut. The Visual Studio designer did generate the code to bind the event handler to the form though:
private void InitializeComponent()
{
// ...
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown);
// ...
}
So I tried adding a Console.WriteLine() call to the handler to check that it was being called at all, but no luck on that either.
Also, I tried to set a breakpoint on the event binding call (shown just above) and found that the program reaches that breakpoint just fine. But any breakpoints I set within the method definition itself are never reached.
To make sure I was doing the first few steps correctly, I tried repeating them with:
A new form in the same solution.
Same issue: the form doesn't respond when I press my Ctrl+Alt+O keyboard shortcut and the debugger isn't even stepping into the event handler. Tried this again and it works.
A brand new WinForms solution.
It works perfectly: the message dialog appears (the Console.WriteLine() call also works).
So I'm quite lost here. What's preventing all the forms in this one project from receiving KeyDown events?
Does your form have KeyPreview property set to true?
Form.KeyPreview Property
Gets or sets a value indicating whether the form will receive key
events before the event is passed to the control that has focus.
http://msdn.microsoft.com/en-us/library/system.windows.forms.form.keypreview.aspx
The most common piece of advice for this problem on StackOverflow and the MSDN1, 2 (including the accepted answer here) is quick and easy:
KeyDown events are triggered on a Form as long as its KeyPreview property is set to true
That's adequate for most purposes, but it's risky for two reasons:
KeyDown handlers do not see all keys. Specifically, "you can't see the kind of keystrokes that are used for navigation. Like the cursor keys and Tab, Escape and Enter for a dialog."
There are a few different ways to intercept key events, and they all happen in sequence. KeyDown is handled last. Hence, KeyPreview isn't much of a preview, and the event could be silenced at a few stops on the way.
(Credit to #HansPassant for those points.)
Instead, override ProcessCmdKey in your Form:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
if (keyData == Keys.Up)
{
// Handle key at form level.
// Do not send event to focused control by returning true.
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
That way, all keys are visible to the method, and the method is first in line to see the event.
Note that you still have control over whether or not focused controls see the KeyDown event. Just return true to block the subsequent KeyDown event, rather than setting KeyPressEventArgs.Handled to true as you would in a KeyDown event handler. Here is an article with more details.
Try setting the KeyPreview property on your form to true. This worked for me for registering key presses.
in my c#/winforms application i would like to do something like application wide keyboardshortcuts, which should be triggered anywhere, except if the focus is in a control where the user can edit text, like a textbox.
currently i am overwriting this function to do this.
protected override bool ProcessCmdKey(ref Message msg, Keys keyData);
how can i add an exception to this, that it is not triggered when the user is in an editable control?
thanks!
I see 2 solutions here.
1) in each editable control, handle all keyboard events and in the eventArgs object, set the Handled property to true;
e.Handled = true;
2) before executing the wide keyboard shortcut, look for the control which has the focus, and if it's a TextBox, ignore it. There's probably a method in each Form to tell what Control has the focus.
The second option is cleaner. I don't give code because I don't have Visual Studio open right now, but if you need more specific code you can ask.
PS: here, I did some googling for you: How to Get FOcused Control?