When coding a small game, I encountered a problem; my form's KeyDown and KeyUp events don't fire at all.
This is the form's code:
public class GameForm : Form
{
private ControllableBlock player;
public GameForm()
{
KeyDown += Game_KeyDown;
KeyUp += Game_KeyUp;
player = new ControllableBlock();
Controls.Add(player);
}
private void Game_KeyDown(object sender, KeyEventArgs e)
{
player.ReactToKey(e.KeyCode);
}
private void Game_KeyUp(object sender, KeyEventArgs e)
{
player.ReactToKey(e.KeyCode);
}
}
There's a lot more going on, but I only pasted the relevant code.
I've already tried setting this.KeyPreview = true; and calling this.Focus();, neither works.
The problem is not in ReactToKey() method, I've already set a breakpoint there and the event is never fired.
Edit: After some tests I've come to a conclusion that the problem is within my ControllableBlock.
Yet, I have no idea why, but I'm working on it.
If I comment out everything that's related to the player, the events start firing.
Edit 2: Seems like the problem is me inheriting my ControllableBlock from Control.
If I inherit it from Panel, it works fine.
Why is this?
Can't I fire an event if I inherit from control?
The ControllableBlock class is empty for now, so it doesn't even do anything other than inherits from Control.
Edit 3: Now that I've started a bounty, I'd like to clarify that I'm not looking for a solution on how to make the events fire, I'm looking for a reason on why they don't fire if I inherit from Control.
If your events should be application-wide try to set property KeyPreview to true - it will allow you to fire respective events regardless of focused control.
this.KeyPreview = true;
Otherwise you should attach these events directly to control that will process them.
Edit:
I removed InitializeComponent(); from my form and got behaviour identical to yours.
After implementing solution provided in this question all events started to qork perfectly.
Copy code snippet here:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
if (keyData == Keys.Left) {
// Do your staff for Left Key here
return true;
}
// you need to add if condition to every direction you want to handle
return base.ProcessCmdKey(ref msg, keyData);
}
I was able to reproduce a similar issue (which is hopefully related..)
Explanation:
Controls which return CanSelect==true are selectable for keyboard input
A blank descendent of Control() is selectable, one of Panel() is not
The first selectable control added to a form will get selected
A selected control will steal keyboard events from its parents by default
Certain keys used for navigation within a window require extra steps to be handleable
Check here for a good overview of how windows keyboard input works.
Code to reproduce it:
public class GameForm : Form
{
public GameForm()
{
this.KeyDown += Game_KeyDown;
var tests = new List<Control[]>() {
new[] { new Panel() },
new[] { new Panel(), new Panel() },
new[] { new Control() },
new[] { new Control(), new Panel() },
new[] { new Panel(), new Control() }
};
// When test index 2 to 4 used, keyboard input does not reach form level
Controls.AddRange(tests[0]);
// When uncommented, ensures all keyboard input reaches form level
/*this.KeyPreview = true;
// Additional plumbing required along with KeyPreview to allow arrow and other reserved keys
foreach (Control control in this.Controls)
{
control.PreviewKeyDown += control_PreviewKeyDown;
}*/
}
void control_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
e.IsInputKey = true;
}
private void Game_KeyDown(object sender, KeyEventArgs e)
{
// breakpoint here
Debug.WriteLine(e.KeyCode);
}
}
You need to make your control selectable before it can receive the focus.
Try adding the following to your constructor:
this.SetStyle(ControlStyles.Selectable, true);
And ensure that you give your form focus after it has been displayed. Or, override OnMouseDown() and call this.Focus() in it.
try moving the handler setup to the Form_Load event rather than the constructor. Should there not be a call to Initialize() in the constructor? I wouldn't particularly recommend removing it
If ControllableBlock inherits from Panel, it will have more event hookups and better UI interaction setup than a base Control object.
Related
Hey stackoverflow community. I have a question that I've been trying to figure out for several days now and I was hoping for some help and general advice.
I have been developing a small application. The main form has a few controls some of which open dialog boxes: an OpenFileDialog, SaveFileDialog, and then a custom dialog for a specific task. The problem arose in the custom dialog box. The basic functionality in a part of the custom dialog box was the ability to click a button that would allow the user to press a key, that key would then be "read" and input into a variable for later use.
My initial thought was to override WndProc and have a check for a bool in it, if the bool was true, the data from a WM_KEYDOWN message would be stored in a variable and the bool would be set to false. I quickly figured out that doesn't work since Dialog Boxes don't get messages like a normal form does.
My question is what is the best/recommended way to achieve this functionality? I've tried some ad hoc workarounds like just using a regular form and disabling the main form while the second form is active, but that didn't work either and I figured it would be better to ask for advice before I continued.
Any help is greatly appreciated.
Edit
Here is some code to demonstrate my current issue
This has quite a few lines removed since I don't think it would be helpful to post hundreds of lines in here
internal class GUI
{
// This class has all the code for the main form
// this is one of the Event Handlers that calls the second dialog box
private static void addItemAbove_Clicked(object sender, EventArgs e)
{
ActionItemDialog aid = new ActionItemDialog();
if(aid.ShowDialog() == DialogResult.OK)
{
ActionList.Items.Insert(list.SelectedIndex, aid.ActionItem);
}
}
}
internal class ActionItemDialog : Form
{
// This class is a custom dialog box for the user to input some data
private bool keyCaptureOn = false;
// This event handler is attached to a button on the dialog box
private void getKey_Clicked(object sender, EventArgs e)
{
getKey.Text = "Press Any Key";
keyCaptureOn = true;
}
// This is the way I was originally trying to get the keypress
protected override void WndProc(ref Message m)
{
if(m.Msg == 0x100)
{
if(keyCaptureOn)
{
// have never been able to make it in this.
}
}
base.WndProc(ref m);
}
}
Also it is worthy to note, I have already tried what all the other questions about this have recommended. I've tried setting KeyPreview to true. I've also made a regular form and used Form.Show() and had it emulate a Modal Dialog but I was still never able to get into that second if statement.
In your ActionItemDialog form, you can add a filter via IMessageFilter to trap keypresses.
When we want to trap a key, we call filter.trapKey = true;, which will fire off a custom Key() event when the user presses a key.
When the form is dismissed, we remove the filter and event subscription.
Here's an example:
public partial class ActionItemDialog : Form
{
private MyFilter filter;
public ActionItemDialog()
{
InitializeComponent();
this.FormClosing += ActionItemDialog_FormClosing;
filter = new MyFilter();
filter.Key += Filter_Key;
Application.AddMessageFilter(filter);
}
private class MyFilter : IMessageFilter
{
public bool trapKey = false;
public delegate void dlgKey(int key);
public event dlgKey Key;
public bool PreFilterMessage(ref Message m)
{
if (trapKey && m.Msg == 0x100)
{
trapKey = false;
Key?.Invoke((int)m.WParam);
return true;
}
return false;
}
}
private void getKey_Click(object sender, EventArgs e)
{
getKey.Text = "Press Any Key";
filter.trapKey = true;
}
private void Filter_Key(int key)
{
getKey.Text = key.ToString();
}
private void ActionItemDialog_FormClosing(object sender, FormClosingEventArgs e)
{
if (filter != null)
{
Application.RemoveMessageFilter(filter);
filter.Key -= Filter_Key;
filter = null;
}
}
}
Note that the filter will only trap key presses when YOUR app is in focus (any form of your app). So if the user clicked to trap a key press, but then switches to another window before pressing the key, then it will not be captured.
To capture a keystroke when ANY application is in focus, you'd have to wire up a low level keyboard hook via WH_KEYBOARD_LL. This would be overkill since I'd expect the key press to be captured only when that app is in focus. This could be an alternate approach, though, if you need to trap key presses globally, as registering a hotkey fails if another app already took the combo you want.
All of my controls inherit from a base class that creates and assigns a OnAccept and OnCancel to the Enter and ESC keys.
private readonly Button _accept, _cancel;
public ViewUserControl()
{
_accept = new Button();
_cancel = new Button();
_accept.Click += (o, e) => OnAccept();
_cancel.Click += (o, e) => OnCancel();
}
// the base function depends on the child functions to implement a accept/cancel function, if it doesn't then those events will fire to the
// new button and not be used for anything
public virtual IButtonControl GetAcceptButton()
{
return _accept;
}
public virtual IButtonControl GetCancelButton()
{
return _cancel;
}
protected virtual void OnAccept() { }
protected virtual void OnCancel()
{
this.ClosingEvent();
}
However, when the user is in a multiline textbox, the enter key is kicking off the OnAccept of the form rather than putting a new line into the textbox (which is the expected behaviour).
Currently, to get around this, i have to find the focused control of the form and if it's a textbox, then manually put the newline in. However when I do this, the cursor resets to the start of the textbox.
protected override void OnAccept()
{
var focused = FindFocusedControl(this);
if (focused is TextBox)
{
focused.Text += Environment.NewLine;
}
else
{
base.OnAccept();
}
}
public static Control FindFocusedControl(Control control)
{
var container = control as ContainerControl;
while (container != null)
{
control = container.ActiveControl;
container = control as ContainerControl;
}
return control;
}
My questions are:
Is there a way to bypass the OnAccept event so the enter event is recognised by the textbox?
Is there a way to call the textbox's enter event manually?
How do I set the cursor to the end of the textbox after I manually put in a line break?
An answer to any of these questions will achieve the result that I'm after, ordered in preference of solution.
UPDATE:
I did find a way to move the caret (not cursor as I called it in the original question) to the end using RichTextBox.SelectionStart however, I'd prefer a more elegant solution.
UPDATE 2:
For anyone else with the same problem, this is what I now do:
From the child control:
txtDetails.GotFocus += (o,e) => AcceptButtonStatus(false);
txtDetails.LostFocus += (o, e) => AcceptButtonStatus(true);
From the base control:
protected void AcceptButtonStatus(bool enabled)
{
this.ParentForm.AcceptButton = enabled?_accept:null;
}
So whenever the textbox gets focus, I remove the accept button from the form.
Here is a post on how to externally call component events.
How can I programmatically generate keypress events in C#?
As for the Accept event, your dialog is intercepting that before the control ever sees it. The only thing that might work would be to add a form event that watches focus changes and if the focus is a multiline text control, you set the AcceptButton control for the form to null (assuming you are using AcceptButton and CancelButton to generate the Accept/Cancel events).
Since TabStop does not work on RadioButtons (see linked question), how can I prevent a (WinForm) RadioButton from being tabbed into, but also allow the user to click on the RadioButton, without the tab focus jumping somewhere else.
I've read this and so I thought the following would work:
rbFMV.Enter += (s, e) => focusFirstWorkflowButton();
rbFMV.MouseUp += (s, e) => rbFMV.Focus();
But it doesn't. When I click on the RB, the focus jumps away, and does not come back on Mouse Up.
Any dirty workarounds out there?
Try something like this:
Set TabStop property of the radiobuttons to "false" in the form's constructor. Then attach the following events handlers to the CheckedChanged events of the radiobuttons.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
radioButton1.TabStop = false;
radioButton2.TabStop = false;
}
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
radioButton1.TabStop = false;
radioButton2.TabStop = false;
}
private void radioButton2_CheckedChanged(object sender, EventArgs e)
{
radioButton1.TabStop = false;
radioButton2.TabStop = false;
}
}
You can attach these event handlers using lambda aswell, as you have shown in your question.
But the important point here is that whenever a radiobutton is checked/unchecked, it's tabstop property is also modified simultaneously. Hence you need to set it to false everytime that event occurs.
The underlying Win32 RadioButton does not automatically change the TabStop property. However, if you use .NET Reflector you can see that the .NET control runs code to update the TabStop property whenever OnEnter method is called because focus has entered the control or whenever the AutoCheck or Checked properties are modified.
Luckily there is a simple solution to your problem. Just derive a new class that overrides the OnTabStopChanged method and automatically set it back to false again. Here is the impl...
public class NonTabStopRadioButton : RadioButton
{
protected override void OnTabStopChanged(EventArgs e)
{
base.OnTabStopChanged(e);
if (TabStop)
TabStop = false;
}
}
Then always use the NonTabStopRadioButton in your application instead of the standard one.
only one control can have input focus at the time i think, so when they click the radio button it will get focus..
But what if you do something like this?
rbFMV.GotFocus += (s, e) => someothercontrol.Focus();
also, have you looked at the TabStop property?
-edit-
i see you have, sorry, missed that :/
I have a windows form with a panel on the left, which consists purely of radiobuttons, and a tabcontrol in the middle, with multiple tab pages within it. Each of these individual tabpages have a series of datagridviews within it, which are shown and hidden depending on which radio button you check.
I accomplish this effect by having each of the radiobuttons on the left assigned a CheckChanged event, which loops through all of the controls within the tabpagecontrol.SelectedTab, and calls .Show() on the corresponding datagridview and calls .Hide() on the rest so that only one datagridview is visible at one time.
My problem occurs when i try to programmatically check one of these RadioButtons. Lets say in Method X, I write RadioButtonA.checked = true. This triggers the usual CheckedChange event handling, which loops through all the datagridviews on the currently selected tabpage and calls .Hide() on everything except the one datagridview form that the radiobutton is supposed to bring up and calls .Show() instead. However, on one of these .Hide() calls on the datagridview, it ends up triggering the RadioButtonA.CheckedChange event AGAIN for a second time. When i look at the sender argument passed to the function, it shows that the sender is the RadioButton i just programmatically clicked on.
I am adding these datagridviews programmatically and can confirm that there are no eventhandlers assigned whatsoever to them. Can anyone help me determine what is causing this additional event to get triggered? Thanks.
For obnoxious change events that trickle through and upset other event handlers on my forms, I've found the only solution is to add a small boolean value:
bool radioIng;
void MyMethod() {
radioIng = true;
try {
radioButton1.Checked = true;
// etc.
} finally {
radioIng = false;
}
}
void radioButton_EventHandler(object sender, EventArgs e) {
if (radioIng) return;
// rest of code here
}
EDIT:
Alternately, you could just remove all of your event handlers and reconnect them later:
void MyMethod() {
try {
radioButton1.CheckChanged -= radioButton_EventHandler;
radioButton2.CheckChanged -= radioButton_EventHandler;
radioButton3.CheckChanged -= radioButton_EventHandler;
// execute your code
radioButton1.Checked = true;
} finally {
radioButton1.CheckedChanged += new EventHandler(radioButton_EventHandler);
radioButton2.CheckedChanged += new EventHandler(radioButton_EventHandler);
radioButton3.CheckedChanged += new EventHandler(radioButton_EventHandler);
}
}
void radioButton_EventHandler(object sender, EventArgs e) {
if (sender == radioButton1) {
// code here to handle
} else if (sender == radioButton2) {
// code here to handle
} else if (sender == radioButton3) {
// code here to handle
}
}
I'm working with a windows form app which I have treeview to show the list of folders , and I have attached NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) event.
and on click of node I call server method to populate the treeview.
Here I could see that NodeMouseClick for one of my tree node is not at all getting triggered.
however for rest of the nodes its working fine with no issues. can anyone tell me what is the exact reason that its not getting triggered.
and I dont want to use After_Select event.
public Form1()
{
InitializeComponent();
Init();
}
private void Init()
{
treeView1.Nodes.Add("root");
for (int i = 0; i < 23; i++)
{
treeView1.Nodes[0].Nodes.Add(i.ToString());
treeView1.Nodes[0].Nodes[i].Nodes.Add("child" + i.ToString());
}
treeView1.Nodes[0].Expand();
}
use treeview of size = 280,369
As I mentioned before in the comments, the workaround is to drop down to the level of the Windows API, intercept mouse messages, and raise the node click event yourself. The code is ugly, but functional.
Add the following code to a new class in your project (I called it CustomTreeView):
class CustomTreeView : System.Windows.Forms.TreeView
{
public event EventHandler<TreeNodeMouseClickEventArgs> CustomNodeClick;
private const int WM_LBUTTONDOWN = 0x201;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_LBUTTONDOWN) // left mouse button down
{
// get the current position of the mouse pointer
Point mousePos = Control.MousePosition;
// get the node the user clicked on
TreeNode testNode = GetNodeAt(PointToClient(mousePos));
// see if the clicked area contained an actual node
if (testNode != null)
{
// A node was clicked, so raise our custom event
var e = new TreeNodeMouseClickEventArgs(testNode,
MouseButtons.Left, 1, mousePos.X, mousePos.Y);
if (CustomNodeClick != null)
CustomNodeClick(this, e);
}
}
// call through to let the base class process the message
base.WndProc(ref m);
}
}
Then change all references to the System.Windows.Forms.TreeView control in your code to the new CustomTreeView class that you just created. This is a subclass of the existing TreeView control that you want to use instead. In case you're not familiar with subclassing, this is the way we modify the existing functionality, or bolt on new functionality to, an existing control. In this case, we've subclassed the original TreeView control to add the CustomNodeClick event that we'll be raising ourselves whenever we detect that a node has been clicked by the user.
Finally, change the event handler method in your form class to listen for the CustomNodeClick event that we're raising, rather than the buggered NodeMouseClick event you were trying to use before.
Compile and run. Everything should work as expected.
try to use AfterSelect Event it must be triggered after any node selection .