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!
Related
There are a lot of questions about this (one, two, three, four, five), but I tried the fixes in all of them and they either don't work or don't suit my purposes. Here is my basic structure:
User Control
|-Panel
|-Picture Box (several of them, created at runtime, do not exist at design time)
Because I think it is relevant, the Panel has its dock set to "fill" and resize set to "grow and shrink", so it always covers the entire user control. The PictureBoxes always cover a portion of the panel, but usually not all of it (although it is possible).
I am specifically listening for Ctrl + C, and I need a method that can respond regardless of which child has focus. I'd like a method that can listen for arbitrary key presses so I can expand on it later.
One of the answers on the linked pages suggests making a global listener for those key presses, I don't want to do that since I don't want it going off if it is a background application. Another suggests detecting it in the top level form and filtering it down to my User Control. The problem is that the User Control on down is being built out as a DLL, and I don't want to force the application using it to have to implement listening for Ctrl + C, this is something that it should be handling on its own.
Why the links above didn't work for me
1) I have no KeyPreview property to set to true on my UserControl. The second answer on that question suggests overriding ProcessCmdKey, which I did, but the callback is never called no matter what I try.
2) This one also suggests overriding ProcessCmdKey. As I said, it is never called.
3) There is no accept button for me to set to true.
4) The KeyDown and PreviewKeyDown callbacks have both been implemented, neither is ever called.
5) Also suggests ProcessCmdKey.
How can I detect key events at the User Control level regardless of focus? Alternatively, if the above methods I tried should be working, what settings have I missed are preventing it from working?
OP: I am specifically listening for Ctrl + C, and I need a method that can
respond regardless of which child has focus.
If you want to handle a key combination like Ctrl+C from your control even if it doesn't have focus or it's not selectable, you can add an invisible MenuStrip to your user control and add an item to it and assign the shortcut to it. Then handle click event of item and do what you need.
The click event will be raised each time the user press Ctrl+C even if your control doesn't contain focus.
You can also do it using code:
public UserControl1()
{
InitializeComponent();
var menu = new MenuStrip();
var item = new ToolStripMenuItem();
item.ShortcutKeys = System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.C;
item.Click += item_Click;
menu.Items.Add(item);
menu.Visible = false;
this.Controls.Add(menu);
}
void item_Click(object sender, EventArgs e)
{
MessageBox.Show("Ctrl + C");
}
Note
You can't handle key events without having focus, but using a MenuStrip you can trap shortcut keys which you want using above method.
The reason which makes it working is, the Form is ContainerControl and ContainerControl calls ToolStripManager.ProcessCmdKey method in ProcessCmdKey which cause processing shortcuts of all non-contextmenu strips of the ToolStripManager.
For more information take a look at source code for ContainerControl.ProcessCmdKey.
Keystroke events are fired on the control that has the focus. You picked controls that do not like to get the focus, don't show the focus, and have no use for keystrokes themselves. Which does beg the question how the user of your app could possibly know what Ctrl+C is going to do.
I'll assume that Ctrl+C is supposed to copy the image in the PictureBox to the clipboard. So best thing to do is to derive your own class from PB and modify it so it can be selected and shows focus. Add a new class to your project and paste the code shown below. Compile. Drag it from the top of the toolbox, replacing the PB in your user control.
using System;
using System.Windows.Forms;
using System.Drawing;
class SelectablePictureBox : PictureBox {
public SelectablePictureBox() {
this.SetStyle(ControlStyles.Selectable, true);
this.TabStop = true;
}
protected override void OnMouseDown(MouseEventArgs e) {
if (e.Button == MouseButtons.Left) this.Focus();
base.OnMouseDown(e);
}
protected override void OnEnter(EventArgs e) {
this.Invalidate();
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e) {
this.Invalidate();
base.OnLeave(e);
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (this.Focused) {
var rc = this.DisplayRectangle;
rc.Inflate(new Size(-2, -2));
ControlPaint.DrawFocusRectangle(e.Graphics, rc);
}
}
}
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
Here is code for a form with a button(not shown) and a textbox. Pressing any key other than function keys causes the KeyDown Event to fire as expected, the textbox is selected, the cursor flashes and the character of the subsequent keystroke appears in the textbox. When pressing a function key however, although the textbox is selected, the cursor does not flash and the character of the very next keystroke does not appear in the textbox. The characters of subsequent keystrokes do appear as expected.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
textBox1.Select();
}
}
I have been able to circumvent this problem by overriding the ProcessCmdKey method but I am curious to know how to do it with KeyDown.
Thanks!!
These days, the preferred method of reading F[1-12] key presses is to override the ProcessCmdKey method, as you said. However, one (now deprecated) way to allow KeyDown to handle F keys is to set the form's KeyPreview property to true. However, as you can see in this post, there are disadvantages to this approach, so it's safer to use ProcessCmdKey.
I want to do a map editor for a game. Its a program that will have Windows Forms UI (like propertyGrid to edit object's properties) but it will also have a panel on which map will be drawn.
What i want:
When focus is on the panel with the map, i'd like to use keyboard to move map around (arrow keys), add objects (number keys) etc. When focus isnt on this panel, i'd like the buttons to work as normal in windows forms - allow to tab between controls etc.
My form looks like this:
It has a ToolStripControl that has a menuStrip (for main menu) and a statusStrip (for status bar). In the middle of the form (or toolstripcontrol), SplitControl is docked (dock=fill) that has two panels. Panel 1 has the PanelMap - a Panel that displays the map, Panel 2 has all other stuff like propertygrid, tabcontrols, buttons etc.
I have KeyPreview of form set to true and process keyboard events in form's keydown event handler.
Now, what happens is if i assign focus to PanelMap, next time i press an arrow key, NO KeyDown event fires. Not a single one! Even form which is supposed to process all events because it has "KeyPreview" doesnt get its even to fire. When i press an arrow, PanelMap loses focus towards the SplitControl.
Okay, i thought, maybe PanelMap is not supposed to ever have focus, lets give focus to SplitControl (if i press arrow key while it has focus, i can handle it so it doesnt go further). But then, if anything like a textbox that is inside something that is inside SplitControl has focus, then SplitControl CANNOT get focus. .Focus() will do nothing - focus remains in the whichever control that had it!
Why does it act so strange? Why doesnt Form's KeyDown fire when panel has focus and arrow key is pressed? Why doesnt SplitControl get focused when i call .Focus() even though CanFocus=true?
And ultimately, how do i achieve what i want? Is there a way to do it?
I think you're running into the widgets taking the keystrokes before your events get to them for navigation. I had this issue, and did this:
private void RemoveCursorNavigation(Control.ControlCollection controls)
{
foreach(Control ctrl in controls)
{
ctrl.PreviewKeyDown += new PreviewKeyDownEventHandler(MainWin_PreviewKeyDown);
RemoveCursorNavigation(ctrl.Controls);
}
}
I call this function in the main form's Load handler, like this:
RemoveCursorNavigation(this.Controls);
In your PreviewKeyDown handler, you need to do this:
public void MainWin_PreviewKeyDown(Object sender, PreviewKeyDownEventArgs e)
{
switch(e.KeyCode)
{
case Keys.Up:
case Keys.Down:
case Keys.Left:
case Keys.Right:
e.IsInputKey = true;
break;
default:
break;
}
}
The e.IsInputKey = true; tells the outside that you've used this event, and don't want it going anywhere else.
Now you get to see the keystrokes before they go to the widgets, and you won't get navigation between them from the cursor keys.
I found an answer like this:
I made a textbox that is hidden under a panel (but enabled and visible). This textbox is given focus when i want to "lock" focus on my PanelMap. It has onkeydown even with e.suppress = true so that textbox never gets any keystrokes to affect it.
Crude workaround but works wonders... typical M$ business...
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.