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.
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);
}
}
}
Here's my setup: I have a TabControl with numerous tabs, and on the last tab, I have a UserControl that is added to a TabPage. This UserControl has a VisibleChanged event that is supposed to check if it is visible, and if it is displays certain information using CustomCommand().
void MyUserControl_VisibleChanged(object sender, EventArgs e)
{
//MessageBox.Show("");
UserControl us = sender as UserControl;
if (us.Visible)
{
CustomCommand();
}
//MessageBox.Show("");
}
Here is the problem: this code as-is will incorrectly think the UserControl is visible when it is not, and the CustomCommand() will run when it's not supposed to. When the commented-out MessageBox function is un-commented in either spot, the UserControl is correctly seen as not visible until, of course, the TabPage is selected. Does anyone have any idea why this would be?
Just putting it on a TabPage doesn't explain this problem. There must be other layout events involved that make the control actually visible later. The MessageBox.Show() call provides the time machine to get the Visible property checked with a delay.
The standard technique to get code to run later, the way MessageBox does, is by using the Control.BeginInvoke() method. The delegate target runs when all events have been fired and processed and the UI thread goes idle again. Like this:
void MyUserControl_VisibleChanged(object sender, EventArgs e)
{
UserControl us = sender as UserControl;
this.BeginInvoke(new Action(() => {
if (us.Visible)
{
CustomCommand();
}
});
}
Not sure if this will help you, but there is a strange, and afaik undocumented, asymmetrical behaviour with the Visible_Changed event:
It does get raised whenever the Visible property of the control changes, either to true or to false.
It also get raised whenever the Parent's Visible property changes to true and only to true!
I could not raise the event by hiding the Control by another control, or for that matter by the MessageBox, though.
I don't know how your application works, so I could not reproduce. But I did notice that indeed, when changing tabs the Visible_Changed event of a Control on the TabPage does get raised, whenever the page is selected (but not when it is unselected.) I didn't know either.
You can try to catch the other direction by either going for the SelectionIndexChanged or by hooking into the VisibleChanged event of the TabPage. This is not visible in the IDE, but it does work both ways:
tabPagexyz2.VisibleChanged += tabPagexyz_VisibleChanged;
void tabPage2_VisibleChanged(object sender, EventArgs e)
{
// do something
}
I saw wild guesses about UI race conditions being behind it, which I doubt. This would at least explain why a MessageBox would habe an influence. If you want to test, you could replace it with one the evil Applictaion.DoEvents.
I have this simple code, where when the user leaves the TextBox control, TreeView gets focused:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.treeView1.Nodes.Add("A");
this.treeView1.Nodes[0].Nodes.Add("A.A");
this.treeView1.Nodes.Add("B");
this.treeView1.Nodes[0].Nodes.Add("B.A");
}
private void textBox1_Leave(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Leave..");
this.treeView1.Focus();
}
}
If we execute this code the Leave event is fired twice:
Leave..
Leave..
But if we set focus to other control, only one Leave event is fired.
Is that a problem of the TreeView? Do you know any workaround? Should we report this to Microsoft?
Thanks,
RG
this.treeView1.Focus();
Do not use the Focus() method in an event handler that's called because of a focusing event, like Leave. If you need to prevent a focus change then use the Validating event instead. Setting e.Cancel = true stops it.
But do note that this isn't very logical to do so for a TreeView, there isn't anything the user can do to alter the state of the control. You'll trap the user. Maybe that was the intention, do make sure the user can still close the window. If not then you might need the FormClosing event to force e.Cancel back to false.
Given that there is no code there to wire up the event I'm guessing you did it from the designer which means a line of code such as
textBox1.Leave += new EventHandler(textBox1_Leave);
will have been added to the Form1.designer.cs, check this file to ensure the line doesn't exist more than once as for each time this line is run you will get an event trigger, so if you run the line 3 times the Leave event will fire 3 times when you leave the textbox!
HTH
OneShot
I have to process enter (among other keys) on win form without it producing error sound, but only if the currently active control didn't process it already.
So, when enter is pressed while in a TextBox or DateTimePicker, i want to process it with a form (without error sound), but if it is pressed, for example, in DataGridView i want it to be handled the way DataGridView does by default.
OnKeyUp solves my problem with handling only unhandled keystrokes (e.Handled) and ProcessCmdKey (this) solves sound problem, but neither solves both.
Any suggestions?
Kudos for the very interesting question. Unfortunately, I can't seem to find a global event handler for all key presses other than overriding ProcessCmdKey on the main form per this article. Only issue with this method is that the arguments passed into the event handler delegate don't define what control is creating the event :(
So, my only thought is that you need to assign your event handler to every single control in the application. I've written some code that should help show you how to do this. I'm not sure what the adverse effects may be of assigning a KeyPress event handler to every control on your page though, but it's the only feasible solution I see.
Code:
private void Form1_Load(object sender, EventArgs e)
{
AssignHandler(this);
}
protected void HandleKeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.Enter && (sender != this.textBoxToIgnore || sender ! this.gridViewToIgnore))
{
PlaySound(); // your error sound function
e.Handled = true;
}
}
public void AssignHandler(Control c)
{
c.KeyPress += new KeyPressEventHandler(HandleKeyPress);
foreach (Control child in c.Controls)
{
AssignHandler(child);
}
}
using c# winforms vs2008
I've got a textbox on a form with a method being called from the textBox1_Leave event. The method takes the contents of the textbox in question and populates other textboxes based on the contents.
My problem is that is the user has focus on the text box then clicks the button to close the form (calling this.close) then the form does not close because the textbox leave event gets fired.
If the textbox does not have focus on form close then the form closes fine.
If however a user closes the form by clicking the little X close icon in the top corner the it closes fine all the time with out the textbox leave event being fired.
How can I duplicate the X close functionality so that I can always close the form without the textbox leave event being fired?
The simplest solution is going to be to check which control is actually focused before doing your post-processing - but you can't do it in the Leave handler, because the focus will still be on the text box at that point.
Instead, you need to move your logic to the LostFocus event, which is not in the designer. You'll have to wire it up at runtime:
public class Form1 : Form
{
public Form1()
{
InitializeComponent();
textBox1.LostFocus += new EventHandler(textBox1_LostFocus);
}
private void textBox1_LostFocus(object sender, EventArgs e)
{
if (closeButton.Focused)
return;
// Update the other text boxes here
}
}
The LostFocus event happens to fire after the new control receives focus.
Clarification - you might find that it works by putting this logic in the Leave event - if the focus is changed by the mouse. If the keyboard is used instead, you'll get the wrong behaviour. LostFocus is reliable in both cases - the focused control will always be the "new" control. This is documented on MSDN: Order of Events in Windows Forms.
Incidentally, the reason why you're not having this problem with the "red X" is that the X is not actually a control that can receive focus, it's part of the window. When the user clicks that, it's not causing the text box to lose focus, and therefore isn't causing the Leave event to fire.
Another approach:
Use the textbox's validating event instead of it's leave event, then change the button's CausesValidation property to false. You will also have to set the textbox to not cause validation in the button's click event so the validating event will not fire when the form is closing (thanks to #Powerlord for pointing this out).
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.CausesValidation = false;
this.Close();
}
You could also handle the FormClosing event and make sure the e.Cancel argument does not get set to true by the validating events on the other controls on the form. I think they will be fired off before the FormClosing event.
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = false;
return;
}
}
you can check to see which control has just got focus.
private void textBox1_Leave(object sender, EventArgs e)
{
if (btnClose.Focused)
return;
// go from here
}
Just check if the form owning the textbox is disposing? If it's getting closed, it's disposing. If it's disposing you could simply end the pesky 'leave' event without doing anything. I didn't check it and forgive me, I'm choked on a project of my own so and I was searching myself, so I don't think I'll have time for that.
private void GuiltyTextBox_Leave(object sender, EventArgs e) {
Form formOwningTheTextBox=(Form)((Control)sender).TopLevelControl;
if (formOwningTheTextBox.Disposing || formOwningTheTextBox.IsDisposed) return;
.......
}
I just believe this is going to work with minimum effort and wanted to send a quick answer before I resume searching my own answer.
Write Following line of code in text box leave event on top
if me.closing then
return
end if