Win Forms UserControl not detecting key presses - c#

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);
}
}
}

Related

Click event not working on dynamically added button or Panel

I am using following code in C# to add a Button
Button TextLabel = new Button(); //local variable
TextLabel.Location = new Point(0, 0);
TextLabel.Visible = true;
TextLabel.Enabled = true;
TextLabel.AutoSize = true;
TextLabel.Click += click;
this.Controls.Add(TextLabel);
And its click handler is
protected void click(object o, EventArgs e)
{
MessageBox.Show("hello");
}
Though the Button is visible and responding to mouse hover, but nothing is happening on its click. What could be wrong or missing?
If I write this same code in an independent project, it works!!!!! Strange. but why????
Form Properties: (if required)
1. Show in taskbar: false
2. Borderless
3. 50% Opaque
Today I realised that just registering click event for a control will not make any event to work unless its parent (in my case its form) on which that control is still active.
Parent control will receive event notification earlier than its child controls. This is a simple and obvious observation, but if not paid attention will make undesirable effects.
That's the mistake I did, I made another form active on my form activated event, hence any control in it didn't received events like mouse clicks.
Talking of 'hover effects are working', then yes, even if a form is inactive, hover works.
So I just removed the line of code that made another form active and everything is working fine now.
private void Form1_Activated(object sender, EventArgs e)
{
//if (form2!=null) form2.BringToFront(); //commented this
}

How to tell if a ChildWindow is the 'top most' window

I'm working in Silverlight, but potentially a WPF solution would work as well.
My problem is very simple. I have lots of modal Child Windows that can be open, and in their generic menu is a home button. This button is supposed to close all of the child windows and return to the base screen. I have a few different types of 'generic child windows' that host lots of different UserControls, so by far the easiest way to implement this is to, when the window comes into focus, check if the global ReturnToHome bool is true, and if it is, just close it.
I've tried all of these
private void ChildWindow_GotFocus(object sender, RoutedEventArgs e)
{
if (CommonResources.ReturnToHome) DialogResult = false;
}
private void ChildWindow_MouseEnter(object sender, MouseEventArgs e)
{
if (CommonResources.ReturnToHome) DialogResult = false;
}
private void ChildWindow_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (CommonResources.ReturnToHome) DialogResult = false;
}
The issue is, GotFocus doesn't fire until I actually click on the window. MouseEnter is a little better, but doesn't fire until I move the mouse. IsEnabledChanged never fires because the Child Window doesn't disable anything. Checking every child window when it closes to see if Home has been clicked isn't easy because of the sheer number of places where you can open child windows, and several of them are nested within User Controls where I couldn't even easily access DialogResult. Any idea how I could do this?
Also I should note that I want each of the windows to close one by one, from top down, because each window that closes does its own verification to see if it should warn the user before closing (giving the user the option to cancel closing)
TopMost is a bool property which is either set to true or false and as far as I'm aware, there is no public property like Z-Index that will tell you the order in which your Windows were set to TopMost. However, there is a simple solution... just maintain a static int variable that will register this order. Each time you add a new Window, set the number into its Tag property:
Window childWindow = new Window();
childWindow.Tag = currentWindowNumber++;
...
childWindow.ShowDialog();
Then, when you want to close them in order, you can just do something like this:
foreach (Window window in Application.Current.Windows.OfType<YourWindowType>()
.OrderBy(w => (int)w.Tag))
{
((AnimationWindow)window).CloseWindow();
}

Stop TextChanged event from firing Leave event

I'm working on a simple WinForms application for a public school where users can identify themselves by entering either their network IDs (which are not protected information) or their system IDs (which are protected information). I want to switch to a password character when the program detects a system ID (which is working just fine); however, when I do this, my application also fires the textbox's Leave event, which tells users to fix a problem with the login data...before there's even a problem.
Here's my code:
void login_TextChanged(object sender, EventArgs e)
{
login.UseSystemPasswordChar = login.Text.StartsWith(<prefix-goes-here>);
}
private void login_Leave(object sender, EventArgs e)
{
if (login.Text.StartsWith(<prefix-goes-here>) && login.Text.Length != 9)
{
signInError.SetError(login, "Your System ID must be nine digits.");
login.BackColor = Color.LightPink;
}
else if (login.Text.IsNullOrWhiteSpace())
{
signInError.SetError(login, "Please enter your username or System ID.");
login.BackColor = Color.LightPink;
}
else
{
signInError.SetError(login, string.Empty);
login.BackColor = Color.White;
}
}
Ultimately, I don't know that this will cause a ton of problems, and I could move this validation step to the Click event of the sign in button on my form, but I'd rather do validation piece-by-piece if possible.
Putting the TextBox inside a GroupBox does reproduce that behavior-- which is odd.
If you want to keep your GroupBox, here is a work around:
private void login_TextChanged(object sender, EventArgs e)
{
login.Leave -= login_Leave;
login.UseSystemPasswordChar = login.Text.StartsWith(<prefix-goes-here>);
login.Leave += login_Leave;
}
For whatever reason, the Leave event fires when the login TextBox is inside a GroupBox control. Replacing the GroupBox with a simple Label control prevented the code within the TextChanged event from firing the Leave event.
Yes, this is a quirk of the UseSystemPasswordChar property. It is a property that must be specified when the native edit control is created (ES_PASSWORD). Changing it requires Winforms to destroy that native control and recreate it. That has side-effects, one of them is that the focus can't stay on the textbox since the window disappears. Windows fires the WM_KILLFOCUS notificaiton.
Being inside a GroupBox is indeed a necessary ingredient, Winforms doesn't suppress the Leave event when it gets the notification. Bug.
Many possible fixes. You could set a flag that the Leave event handler can check to know that it was caused by changing the property.

How to capture mouse wheel on panel?

How to capture mouse wheel on panel in C#?
I'm using WinForms
EDIT:
I try to do it on PictureBox now.
My code:
this.pictureBox1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.pictureBox1_MouseClick);
this.pictureBox1.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.pictureBox1_MouseClick);
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
MessageBox.Show("Click");
}
Clicking works. Wheelling doesn't.
Why?
If you can't see the "MouseWheel" event on a component, then you need to create it manually. Also, we need to focus that component, otherwise the "MouseWheel" event will not work for that component. I will show you how to create a "MouseWheel" event for "pictureBox1" and how it works.
INSIDE THE CONSTRUCTOR, create a mousewheel event on that component.
InitializeComponent();
this.pictureBox1.MouseWheel += pictureBox1_MouseWheel;
CREATE THE FUNCTION manually. According to my example, call it "pictureBox1_MouseWheel"
private void pictureBox1_MouseWheel(object sender, MouseEventArgs e)
{
//you can do anything here
}
CREATE a MouseHover event on that component (Go to properties in PicureBox1, select event, locate "MouseHover" and double-click the "MouseHover" event).
CALL "Focus()"; method inside that MouseHover event.
pictureBox1.Focus();
Now run the program.
Windows sends the WM_MOUSEWHEEL message to the control that has the focus. That won't be Panel, it is not a control that can get the focus. As soon as you put a control on the panel, say a button, then the button gets the focus and the message.
The button however has no use for the message, it's got nothing to scroll. Windows notices this and sends the message to the parent. That's the panel, now it will scroll.
You'll find code for a custom panel that can get the focus in this answer.
UPDATE: note that this behavior has changed in Windows 10. The new "Scroll inactive windows when I hover over them" option is turned on by default. The makes the mouse wheel behavior more consistent with the way it works in a browser or, say, an Office program. In this specific case the picturebox now will get the event. Watch out for this.
To wire it up manually...
this.panel1.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.panel1_MouseWheel);
private void panel1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
{
///process mouse event
}
Easier method is in visual studio click on panel, goto properties viewpanel, select events, locate and double click the "mousewheel" event.
In Winforms, this is achieved using the Control.MouseWheel event
Getting mousewheel events is tricky. The easiest way is using
this.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.panel1_MouseWheel);
instead of
this.panel1.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.panel1_MouseWheel);
This way the form gets the event instead of control. This way is easy but has one problem: you can use only one mousewheel event in your form.
If you have more than one control to get mousewheel event the best way is This answer by "Shehan Silva - weltZ"

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.

Categories