Send Keyboard Events from one Form to another Form - c#

my question is quite simple:
Our C# application has one MainForm with a menu and several keyboard shortcuts associated with the menu entries.
Now we need to trigger the menu entries from some child forms too. But since the MainForm is inactive when one of the child forms is active, the shortcuts do not work.
Is there a simple way to propagate all keyboard events from the child form to the 'Owner' form? Or just to another form in general?
Ah, and we cannot use some low level windows stuff, because we need to run the application on Mono/Linux too.
EDIT:
The exact problem i have is to trigger the menu items with the same shortcut from another form. Of course without updating code in the forms if the menu changes of new items are added.

This is what fixed it for me:
public class MainForm : Form
{
public bool ProcessCmdKeyFromChildForm(ref Message msg, Keys keyData)
{
Message messageCopy = msg;
messageCopy.HWnd = this.Handle; // We need to assign our own Handle, otherwise the message is rejected!
return ProcessCmdKey(ref messageCopy, keyData);
}
}
public class MyChildForm : Form
{
private MainForm mMainForm;
public MyChildForm(MainForm mainForm)
{
mMainForm = mainForm;
}
// This is meant to forward accelerator keys (eg. Ctrl-Z) to the MainForm
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (mMainForm.ProcessCmdKeyFromChildForm(ref msg, keyData))
{
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
}

Did you try something like this?
ParentForm : Form
{
public NotifyKeyPress(KeyPressEventArgs e)
{
OnKeyPress(e);
}
}
ChildForm : Form
{
ParentForm _parent;
public ChildForm(ParentForm parent)
{
_parent = parent;
KeyPress += KeyPressHandler;
}
public KeyPressHandler(object sender, KeyPressEventArgs e)
{
if (_parent != null)
{
_parent.NotifyKeyPress(e);
}
}
}

I think you want to set KeyPreview on the parent form to true
When this property is set to true, the form will receive all KeyPress, KeyDown, and KeyUp events. After the form's event handlers have completed processing the keystroke, the keystroke is then assigned to the control with focus. For example, if the KeyPreview property is set to true and the currently selected control is a TextBox, after the keystroke is handled by the event handlers of the form the TextBox control will receive the key that was pressed. To handle keyboard events only at the form level and not allow controls to receive keyboard events, set the KeyPressEventArgs.Handled property in your form's KeyPress event handler to true.
EDIT:
the answer in this question might be helpful:

The ToolStrip.AllowMerge property "gets or sets [...] whether multiple MenuStrip, ToolStripDropDownMenu, ToolStripMenuItem, and other types can be combined." (MSDN).
This means that you can:
"Use the AllowMerge property to enable multiple-document interface (MDI) children to combine their respective menus in the MDI parent." (AllowMerge property, Remark, MSDN)
See also:
MergeAction
MergeIndex
This, I hope, will help you get what you want. Now, I don't know if this is proper to Windows Forms or if it shall work on Linux too once built.

I presume by inactive you mean that it doesn't have focus?
The cleanest way to do this is to have each form expose events that relate to their menus being manipulated. When you create the forms, subscribe them to each other (or from child to MainForm or whatever way the flow needs to go). When the menu is clicked, execute your extra event and the other form will receive this.
Does that help? I believe that this is better than trying to force a message manually as it will be self-documenting code that the forms need to react to each other.
A more "away from the problem" approach, do you need two forms or can you refactor the UI design?

There is much simpler way to do this. Menu items should trigger appropriate method calls.
Then you can call these methods anywhere in application.

Instead of binding key shortcuts to menu items on the main form you can create a custom key-processing method that reacts to the key shortcuts. Put this method in the main form. Then invoke this method from all child forms on a key event. #Adam Driscoll's code is much compatible with this approach.

Related

In Winforms, PreviewKeyDown() never fired for ANY key

I was originally trying to get my program to get inputs of the arrow keys (Up, Down, Left and Right), but found out the hard way that in KeyDown(), those keys never made. Afterwards I found out that I could enable the arrow keys by going into the PreviewKeyDown() function and setting:
e.IsInputKey = true;
with whatever conditionals and logic around it. The trouble was that when I wrote the function:
private void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{ /*whatever logic goes here*/}
it never fired; I even set a breakpoint that would trigger inside the function to be sure. Also, I tried:
this.Focus()
in the constructor to make sure that the main form had the focus, but it made no difference. The only thing that worked was setting the focus to a Button I had created and the button also trigger on a PreviewKeyDown event by calling the above Form1_PreviewKeyDown().
So at this point I have a working method, but can anyone help me understand why it never originally fired? I'm assuming that for some reason the Form's PreviewKeyEvent never fires, but I really have no idea why.
Why
You can try this little experiment: Make a form with two buttons, override PreviewKeyDown(), set a breakpoint, run it, and press the left/right arrow keys. The PreviewKeyDown() method won't be run. But delete the buttons and the override will be called.
The reason for the difference is that WinForms is handling the arrow keys itself for navigation. When you have input controls like buttons and text boxes, WinForms will automatically take over certain special keys like TAB and the arrow keys to navigate from one control to the next. It probably does this because a lot of people like to be able to use the keyboard to navigate, and it's easy to break that for them if you go messing with the navigation keys. Better to handle them for you so you don't mess them up by accident while you're playing with the other keys.
A naive workaround would be to detect when you form loses focus and take it back. This doesn't work though, because your form doesn't lose focus. The input controls have the focus, and they're part of the form, so the form still (technically, indirectly) has focus. It only loses the focus when you click outside on some other window.
A better workaround involves a better understanding of what's going on "under the covers", just below the .Net interpreter. WinForms mimics this level fairly closely, so it's a useful guide to understanding what WinForms is up to.
When Windows sends input (like keystrokes) to your program, your form isn't always the first to get the input. The input goes to whichever control has the focus. In this case, that control is one of the buttons (I'm assuming the focus glow is hidden at first to justify why nothing happens on the first stroke when nothing looks selected).
Once the button gets hold of the input, it gets to decide what happens next. It can pass the input on to whoever's next in line, do something and then pass it on, or completely handle the input and not pass it on at all.
With normal letter keys, the button decides it doesn't know what to do with them and passes them to its base class instead. The base class doesn't know either, so it forwards the key on. Eventually, it hits the Control class, which handles it by passing it on to whichever Control is in its Parent property. If that goes on long enough, your form will eventually get a chance to handle the input.
So in a nutshell, WinForms is giving the input to the most specific target first, then working out to more and more general things until someone knows how to handle the input.
In the case of the arrow keys, however, the button knows how to handle those. It handles them by passing the focus on to the next input control. At that point, the button declares the input totally handled, swallows the key and doesn't give anyone else a chance to look at it. Nobody after the button even knows the keystroke ever happened.
That's why your PreviewKeyDown() override isn't being called. It's only called when your Form gets a keystroke, but it never gets the keystroke because it went to an input control, the input control offered to let the navigation code look at it, and the navigation code swallowed it.
Workaround
Unfortunately, getting around this is going to be some work. The keystrokes are disappearing into the input controls, so you'll need to get all the input controls involved in getting the arrow keys into your form.
To do this, you'll need to derive new controls from all the input control types you use and use them in place of the originals. Then you'll have to override the OnPreviewKeyDown() method in each one and set e.IsInputKey = true. That'll get your arrow keys into the derived controls' KeyDown() handlers instead of having them stolen by the navigation code.
Next, you'll have to handle the KeyDown() event in all those controls, too. Since you want the arrow keys to raise events in the Form, all the derived controls will need to track down their form and pass the keys to that (which means the form's method will need to be public).
Putting all that together, the arrow-key-passing input controls will look about like this.
class MyButton : Button
{
public MyButton()
{
this.KeyDown += new KeyEventHandler(MyButton_KeyDown);
}
protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
{
e.IsInputKey = true;
base.OnPreviewKeyDown(e);
}
private void MyButton_KeyDown(object sender, KeyEventArgs e)
{
Form1 f = (Form1)this.FindForm();
f.Form1_KeyDown(sender, e);
}
}
That's going to be a bit error prone with all the repeated code.
An easier way would be to override your form's ProcessCmdKey() method and handle the keys there. Something like this would probably work:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Up || keyData == Keys.Down ||
keyData == Keys.Left || keyData == Keys.Right)
{
object sender = Control.FromHandle(msg.HWnd);
KeyEventArgs e = new KeyEventArgs(keyData);
Form1_KeyPress(sender, e);
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
This effectively steals the command keys (those special navigation keys) even before the input controls get a chance at them. Unless those controls override PreviewKeyDown() and set e.IsInputKey = true. The child's PreviewKeyDown() method will come first, then the arrow will be considered not a command key and your ProcessCmdKey() won't be called.
ProcessCmdKey() is meant for context menu handling. I'm not sure whether it's wise to go using it for things other than context menus, but even Microsoft recommends it for similar kinds of use and it does seem to work, so it may be worth considering.
Conclusion
Long story short, navigation keys are meant for navigation. Messing with them can make the user experience unpleasant for keyboard users, so .Net makes it hard to get at them so you'll be encouraged to mess with other keys instead.
I had the same problem!
Luckily i found a dense answer :)
you can use the bool function in the definition of the Form class witch occurs on every key pressed. but remember to return the base function!
public partial class myForm : Form
{
public myForm ()
{
InitializeComponent();
}
protected override bool ProcessDialogKey(Keys keyData)
{
//Add your code here
return base.ProcessDialogKey(keyData);
}
}
hopefully i helped. but if my answer is incomplete please note me!
Keyboard events on the parent form are pretty useless unless you also set
this.KeyPreview = true;
see the MSDN documentation

Focus picturebox when entering form

I am creating a simple game for school in C#, where I am controlling a character using the WASD keys. The character is taken from a sprite sheet and put into an imagelist. The imagelist is in a picturebox.
Everything works fine when it's just the picturebox in the form, but when I add a button or something else, it's like it lose focus. It doesn't respond.
I have searched endless pages for a solution to set focus on the picturebox when the form opens, but I haven't found anything that works.
I would really appreciate some help.
Edit: It's WinForms.
The PictureBox cannot take the focus. It is intended as a way to show an image but not intended to allow user input such as via the keyboard.
A crude approach would be to intercept the OnKeyDown event on the Form itself and then test for the keys of interest. This will work as long as the control that has the focus, such as your Button, does not want to process those keys itself.
A better approach would be to override ProcessCmdKey() method of the Form. This method is called on the target control, such as your Button, to decide if the key is special. If the Button does not recognize it as special then it calls the parent control. In this way your Form level method will be called for each key press that is not a special key for the actual target. This allows the Button to still process a ENTER key which is used to press the Button but other keys will be processed by your Form.
Lastly, to intercept all keys before they are handled by any of the controls on the Form you would need to implement the IMessageFilter interface. Something like this...
public partial class MyWindow : Form, IMessageFilter
{
public MyWindow()
{
InitializeComponent();
Application.AddMessageFilter(this);
}
public bool PreFilterMessage(ref Message m)
{
// WM_KEYDOWN
if (m.Msg == 0x0100)
{
// Extract the keys being pressed
Keys keys = ((Keys)((int)m.WParam.ToInt64()));
// Test for the A key....
if (keys == Keys.A)
{
return true; // Prevent message reaching destination
}
}
}
return false;
}
I found event MouseHover with pictureBox1_Hover calling pictureBox1.Focus() worked. When the mouse was hovered over the PictureBox in question, it would gain focus. Other than that, it didn't seem that calling pictureBox1.Focus() during form load had any effect on the focus.
this.pictureBox1.MouseHover += new System.EventHandler(this.pictureBox1_Hover);
private void pictureBox1_Hover(object sender, EventArgs e)
{
pictureBox1.Focus();
}
It worked for me!

How do I capture Keys.F1 regardless of the focused control on a form?

I used KeyDown event and some simple code like if (e.KeyCode == Keys.F1) to capture F1 is pressed on a form BUT if there are some text boxes on the form or if there are some spreadsheets with Dock Fill on the form then the code above gets useless and does nothing. But I want to do something when user presses F1 on this form. so how do we capture a specific keydown event like F1 on the whole form..and I do not want to go to the route that capture the KeyDown of all other controls on the form and pass them to the Form for processing. is there any cleaner way to do this?
Yes, indeed there is. The correct way for the form to handle key events regardless of the control that currently has the input focus is to override the ProcessCmdKey method of your form class:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.F1)
{
MessageBox.Show("You pressed the F1 key");
return true; // indicate that you handled this keystroke
}
// Call the base class
return base.ProcessCmdKey(ref msg, keyData);
}
You return true to indicate that you handled the keystroke and don't want it to be passed on to other controls. If you do want it to be passed on to the event handlers for other controls, simply return false.
And you're best off ignoring the KeyPreview property. That's an anachronism from the VB 6 days and not really the preferred way of doing this in the .NET world. Further reading: Disadvantage of setting Form.KeyPreview = true?
Set the form's KeyPreview to true. This will make sure the form get the keypress messages first and if you handle it, you can set e.Handled = true so it doesn't passed down to the controls.
Turn on KeyPreview and every key press in the form will get routed through it's key event handlers first.
Another way is to Override the ProcessCmdKey function http://msdn.microsoft.com/en-us/library/system.windows.forms.control.processcmdkey(v=VS.100).aspx

Handle clipboard copy on UserControl

I have complex UserControl (grid, edit controls for grid, etc...) and I want handle CTRL+C keyboard shortcut, however I don't want disable a native functions by edit controls (textboxes, comboboxes, etc...). If the CTRL+C is not handled by other inner controls I want handle it by myself (copy whole row(s) from grid, etc...).
I tried override WndProc method in UserControl and check for WM_COPY and WM_COPYDATA, but it doesn't work. It works only on final target control (TextBox for example).
You can do this by overriding ProcessCmdKey(). Check if a text box has the focus. For example:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
if (keyData == (Keys.Control | Keys.C)) {
var box = this.ActiveControl as TextBoxBase;
if (box == null) {
// Do your stuff
MessageBox.Show("Copy!");
return true;
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
What you are looking for is a way to bubble events raised by a child control up to its container so that you can handle those events at the User Control's level.
The automatic propagation of events across the control hierarchy is built into WPF and is called Routed Events. However this functionality is not available out of the box in Windows Forms, so you will have to implement your own solution.
Have a look at this SO question to get some inspiration.
Related resources:
How to route events in a Windows Forms application
I didn't try it out and i think it heavy depends on all the child controls, which are within your UserControl. But normally a keystroke is given to the actual control that has the focus. If it doesn't handle that keystroke (setting e.Handled = true), it would be bubble up to its parent and if that doesn't handle it, it would go further till it reaches the form and finally the limbus.
So if your child controls are properly written and they can't handle the given keystroke (e.g. Control + C) it should be easy to add a handler into your UserControl to the KeyDown event and do whatever you like.
Update
After reading your comments, i still think that the way shown by Enrico and me should be the correct one. So i think the problem is that if one of your 3rd party controls has the focus it is not able to handle the copy shortcut, but it sets the e.Handled = true leading to no further informations of the parent controls about the shortcut.
So at first you should contact your control vendor and send him a bug report about this wrong behaviour.
Alternative there exists another hacky way:
In your form you can set the KeyPreview to true and intercept the incoming key. Now you could check if within the ActiveControl is something that handles the shortcut correctly (maybe a check against a Dictionary<Type, bool> or a HashSet<Type> lackingControls) and just leave the function or do whatever you want and setting the e.Handled = true by yourself.
Update 2
A little snippet to illustrate what i meant:
this.KeyPreview = true;
HashSet<Type> scrappyControls = new HashSet<Type>();
//ToDo: Add all controls that say it handles Ctrl-C
// but doesn't it the right way.
scrappyControls.Add(typeof(TextBox));
this.KeyDown += (sender, e) =>
{
if (e.KeyData == (Keys.Control | Keys.C))
{
if (scrappyControls.Contains(this.ActiveControl.GetType()))
{
//ToDo: Do copy to clipboard on yourself
e.Handled = true;
}
}
};
The drawback of this functionality is, that it must be placed into your form, not into your self-written UserControl. But that way you will be informed, when a TextBox has the focus and Control + C is pressed within.

Adding an F5 Hotkey in C#

I'm making a windows app (WinForms) and would like my application to call a method when the user presses F5 - this should work no matter what the user is doing but they must be using the program - I don't want to use global hooks - any ideas?
Override the form's ProcessCmdKey on your main form and look for F5.
protected override bool ProcessCmdKey (ref Message msg, Keys keyData)
{
bool bHandled = false;
// switch case is the easy way, a hash or map would be better,
// but more work to get set up.
switch (keyData)
{
case Keys.F5:
// do whatever
bHandled = true;
break;
}
return bHandled;
}
Check out the Form.KeyPreview property, it will allow you to trap all KeyDown, KeyUp and KeyPress events at the form level before allowing them to be processed by individual elements.
MSDN Form.KeyPreview page
If you have only one form that can have focus, you can set the KeyPreview property of that form to true and handle the KeyPress event.
The KeyPreview property will cause the form to receive all key presses, regardless which control on the form has the input focus.

Categories