So I'm working on a basic subclass of Label that supports editing. The editing part works fine--I insert a text box with no background color or border on click, commit changes on enter or loss of focus.
The little thing that's giving me trouble is related to some basic font styling. The label is to underline with the MouseHover event (like a hyperlink) and then lose the underline afterwards. Most of the time, it works, but occasionally, the MouseHover will cause the font to revert to the Winforms default--8pt sans-serif--instead of performing the operation.
Here's the event handler:
void BWEditableLabel_MouseHover(object sender, EventArgs e)
{
_fontBeforeHover = Font;
Font hoverFont = new Font(
_fontBeforeHover.FontFamily.Name,
_fontBeforeHover.Size,
_fontBeforeHover.Style | FontStyle.Underline
);
Font = hoverFont;
}
Some of you may observe that the last line doesn't simply say:
Font = new Font(Font, Font.Style | FontStyle.Underline)
I tried that, and the problem came about. The current version before you was an attempt that I made to resolve the issue.
What if you use the MouseEnter and MouseLeave events? MouseEnter sets it to underlined and MouseLeave reverts it.
I think I solved it, though it feels like a bit of a patch instead of the cleanest solution. I did away with _fontBeforeHover and created _originalFont. I then overrode the Font property of the label and in the setter, set _originalFont to whatever the label is being set to. Then, in my MouseHover and MouseLeave events, I used a new method, SetFont() to change the font. Within SetFont(), I assign base.Font instead of using the overridden property. If I used the overridden property, I'd always be reassigning _originalFont to whatever I change the label's font to during the events.
Sure would be nice if I didn't need all that extra code, though :-)
I'm definitely open to more suggestions.
Sorry for the double answer, but maybe you might want to consider drawing the text yourself via DrawString in the paint event, that way you're not setting the Font property. The Font property gets it's value from the Parent's font unless explicitly set.
On your user control create a mousehover Event for your control like this, (or other event type) like this
private void picBoxThumb_MouseHover(object sender, EventArgs e)
{
// Call Parent OnMouseHover Event
OnMouseHover(EventArgs.Empty);
}
On your WinFrom which hosts the UserControl have this for the UserControl to Handle the MouseOver
this.thumbImage1.MouseHover += new System.EventHandler(this.ThumbnailMouseHover);
Which calls this method on your WinForm
private void ThumbnailMouseHover(object sender, EventArgs e)
{
ThumbImage thumb = (ThumbImage) sender;
}
Where ThumbUmage is the type of usercontrol
Related
Following is my code to handle gotfocus and lostfocus event for all textboxes available in form.
private void Form1_Load(object sender, EventArgs e)
{
foreach (Control c in this.Controls)
{
if (c is TextBox)
{
c.GotFocus += new System.EventHandler(this.txtGotFocus);
c.LostFocus += new System.EventHandler(this.txtLostfocus);
}
}
}
private void txtGotFocus(object sender, EventArgs e)
{
TextBox tb = (TextBox)sender;
if (tb != null)
{
tb.BackColor = Color.Silver;
tb.BorderStyle = BorderStyle.FixedSingle;
}
}
private void txtLostFocus(object sender, EventArgs e)
{
TextBox tb = (TextBox)sender;
if (tb != null)
{
tb.BackColor = Color.White;
tb.BorderStyle = BorderStyle.Fixed3D;
}
}
It works fine with first textbox but when I go to next textbox by pressing tab key it will repeatedly call both events and textbox behave like blinking. After some time error message display in code like:
A callback was made on a garbage collected delegate of type 'System.Windows.Forms!System.Windows.Forms.NativeMethods+WndProc::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.
Whats wrong with code? Is there any solution?
c.LostFocus += new System.EventHandler(this.txtLostfocus);
LostFocus is a dangerous event, the MSDN Library article for Control.LostFocus warns about this and strongly recommends to use the Leave event instead. This is something you can see in the designer, drop a TextBox on the form and click the lightning bolt icon in the Properties window. Note how both the GotFocus and LostFocus events are not visible. You must use the Enter and Leave events instead.
Some background on what is going on here. Your program blows up because you assign the BorderStyle property. That's a "difficult" property, it is one that affects the style flag of the window, the one that's passed to the native CreateWindowEx() function. So changing the border style requires Winforms to create the native window again. This is what causes the flicker you see, the text box is destroyed and re-created, then repainted. You see that.
But that has side-effects beyond the flicker, it also causes the low-level GotFocus and LostFocus events to fire. Because the destroyed window of course also loses the focus. This interacts very poorly in your program since your LostFocus event handler changes the BorderStyle again, forcing Winforms to yet again recreate the window. And fire the GotFocus event, you change the BorderStyle yet again. This repeats over and over again, you see the textbox rapidly blinking. This doesn't go on endlessly, after 10,000 times of creating the window, the operating system pulls the plug and doesn't let your program create yet another one. The hard crash on the window procedure is the outcome.
Not a problem with the Enter and Leave events, they don't work from the low-level Windows notification so don't fire when the textbox window is recreated. You can only get rid of the one-time flicker, if it is still bothering you, by not changing the BorderStyle property.
This occurs cause you change the borderstyle. Dont ask me why, i dont know it. If you remove tb.BorderStyle = XXX it will work. I guess(!) changing the borderstyle makes the control loose its fokus (and get it again)
Edit: Hans was faster, with a much better explanation :)
With the following code, when i move the mouse into the button and click it, the final value of result is 2 instead of 1.
static int result = 0;
private void button2_MouseHover(object sender, EventArgs e)
{
result++;
}
private void button2_MouseDown(object sender, MouseEventArgs e)
{
button2.FlatStyle = FlatStyle.System;
}
I had tried different setting:
Using MouseEnter instead of MouseHover give result = 1
Only changing of FlatStyle to System will trigger MouseHover one more time after button is clicked.
If the FlatStyle of button is originally FlatStyle.System, give result = 1;
If the FlatStyle is changed to others Style(popup, flat), give result = 1;
So i guess the problem lies with the FlatStyle set to FlatStyle.System, anyone can explains this to me?
This is a fairly common quirk in Winforms. You can visualize what's going with this code:
private void button1_MouseHover(object sender, EventArgs e) {
Console.WriteLine("{0:X}, {1}", button1.Handle, DateTime.Now.TimeOfDay);
result++;
}
Look in the Output window for the displayed diagnostic. On mine:
12780286, 03:04:10.7619696
12780286, 03:04:16.2935137
12780286, 03:04:20.1062178
12780286, 03:04:23.9970596
Click
12845822, 03:04:28.9191911
12845822, 03:04:32.3099880
Note how the Handle property value changed after the click. In other words, the physical Windows window changed. This happened because you changed the FlatStyle property. Winforms implements this by destroying the button control window and recreating it. If you look really close then you can actually see this, the button briefly flickers.
There are a few properties that are implemented like this, they are "heavy" properties that requires the window to be recreated since the style flags changed. Style flags that are passed in the native CreateWindowEx() call. A change in those style flags requires another call to CreateWindowEx().
This tends to have side-effects. Destroying the old window causes its state to be lost. Winforms tries to restore the state of the new window as best it can, but there are a few it cannot get to. The "window was hovered" state is one of them. Clearly the new window has that internal state flag set to false. Which is why you get the MouseHover event again. You'll need to work around this quirk.
I'm creating a custom dropdown box, and I want to register when the mouse is clicked outside the dropdown box, in order to hide it. Is it possible to detect a click outside a control? or should I make some mechanism on the containing form and check for mouseclick when any dropdownbox is open?
So I finally understand that you only want it to close when the user clicks outside of it. In that case, the Leave event should work just fine... For some reason, I got the impression you wanted it to close whenever they moved the mouse outside of your custom dropdown. The Leave event is raised whenever your control loses the focus, and if the user clicks on something else, it will certainly lose focus as the thing they clicked on gains the focus.
The documentation also says that this event cascades up and down the control chain as necessary:
The Enter and Leave events are hierarchical and will cascade up and down the parent chain until the appropriate control is reached. For example, assume you have a Form with two GroupBox controls, and each GroupBox control has one TextBox control. When the caret is moved from one TextBox to the other, the Leave event is raised for the TextBox and GroupBox, and the Enter event is raised for the other GroupBox and TextBox.
Overriding your UserControl's OnLeave method is the best way to handle this:
protected override void OnLeave(EventArgs e)
{
// Call the base class
base.OnLeave(e);
// When this control loses the focus, close it
this.Hide();
}
And then for testing purposes, I created a form that shows the drop-down UserControl on command:
public partial class Form1 : Form
{
private UserControl1 customDropDown;
public Form1()
{
InitializeComponent();
// Create the user control
customDropDown = new UserControl1();
// Add it to the form's Controls collection
Controls.Add(customDropDown);
customDropDown.Hide();
}
private void button1_Click(object sender, EventArgs e)
{
// Display the user control
customDropDown.Show();
customDropDown.BringToFront(); // display in front of other controls
customDropDown.Select(); // make sure it gets the focus
}
}
Everything works perfectly with the above code, except for one thing: if the user clicks on a blank area of the form, the UserControl doesn't close. Hmm, why not? Well, because the form itself doesn't want the focus. Only controls can get the focus, and we didn't click on a control. And because nothing else stole the focus, the Leave event never got raised, meaning that the UserControl didn't know it was supposed to close itself.
If you need the UserControl to close itself when the user clicks on a blank area in the form, you need some special case handling for that. Since you say that you're only concerned about clicks, you can just handle the Click event for the form, and set the focus to a different control:
protected override void OnClick(EventArgs e)
{
// Call the base class
base.OnClick(e);
// See if our custom drop-down is visible
if (customDropDown.Visible)
{
// Set the focus to a different control on the form,
// which will force the drop-down to close
this.SelectNextControl(customDropDown, true, true, true, true);
}
}
Yes, this last part feels like a hack. The better solution, as others have mentioned, is to use the SetCapture function to instruct Windows to capture the mouse over your UserControl's window. The control's Capture property provides an even simpler way to do the same thing.
Technically, you'll need to p/invoke SetCapture() in order to receive click events that happen outside of your control.
But in your case, handling the Leave event, as #Martin suggests, should be sufficient.
EDIT: While looking for an usage example for SetCapture(), I came across the Control.Capture property, of which I was not aware. Using that property means you won't have to p/invoke anything, which is always a good thing in my book.
So, you'll have to set Capture to true when showing the dropdown, then determine if the mouse pointer lies inside the control in your click event handler and, if it doesn't, set Capture to false and close the dropdown.
UPDATE:
You can also use the Control.Focused property to determine if the control has got or lost focus when using a keyboard or mouse instead of using the Capture with the same example provided in the MSDN Capture page.
Handle the Form's MouseDown event, or override the Form's OnMouseDown
method:
enter code here
And then:
protected override void OnMouseDown(MouseEventArgs e)
{
if (!theListBox.Bounds.Contains(e.Location))
{
theListBox.Visible = false;
}
}
The Contains method old System.Drawing.Rectangle can be used to indicate if
a point is contained inside a rectangle. The Bounds property of a Control is
the outer Rectangle defined by the edges of the Control. The Location
property of the MouseEventArgs is the Point relative to the Control which
received the MouseDown event. The Bounds property of a Control in a Form is
relative to the Form.
You are probably looking for the leave event:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.leave.aspx
Leave occurs when the input focus leaves the control.
I just wanted to share this. It is probably not a good way of doing it that way, but looks like it works for drop down panel that closes on fake "MouseLeave", I tried to hide it on Panel MouseLeave but it does not work because moving from panel to button leaves the panel because the button is not the panel itself. Probably there is better way of doing this but I am sharing this because I used about 7 hours figuring out how to get it to work. Thanks to #FTheGodfather
But it works only if the mouse moves on the form. If there is a panel this will not work.
private void click_to_show_Panel_button_MouseDown(object sender, MouseEventArgs e)
{
item_panel1.Visible = true; //Menu Panel
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (!item_panel1.Bounds.Contains(e.Location))
{
item_panel1.Visible = false; // Menu panel
}
}
I've done this myself, and this is how I did it.
When the drop down is opened, register a click event on the control's parent form:
this.Form.Click += new EventHandler(CloseDropDown);
But this only takes you half the way. You probably want your drop down to close also when the current window gets deactivated. The most reliable way of detecting this has for me been through a timer that checks which window is currently active:
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
and
var timer = new Timer();
timer.Interval = 100;
timer.Tick += (sender, args) =>
{
IntPtr f = GetForegroundWindow();
if (this.Form == null || f != this.Form.Handle)
{
CloseDropDown();
}
};
You should of course only let the timer run when the drop down is visible. Also, there's probably a few other events on the parent form you'd want to register when the drop down is opened:
this.Form.LocationChanged += new EventHandler(CloseDropDown);
this.Form.SizeChanged += new EventHandler(CloseDropDown);
Just don't forget to unregister all these events in the CloseDropDown method :)
EDIT:
I forgot, you should also register the Leave event on you control to see if another control gets activated/clicked:
this.Leave += new EventHandler(CloseDropDown);
I think I've got it now, this should cover all bases. Let me know if I'm missing something.
If you have Form, you can simply use Deactivate event just like this :
protected override void OnDeactivate(EventArgs e)
{
this.Dispose();
}
I have a tab control and need to remove the dotted focus rectangle around the selected tab.
I have set the TabStop property of the TabControl to false. However if I click on a tab and press the Tab key, the dotted rectangle appears around the tabname.
I have tried creating my own TabControl and tried this
class MyTabControl : TabControl
{
public MyTabControl()
{
TabStop = false;
DrawMode = TabDrawMode.OwnerDrawFixed;
DrawItem += new DrawItemEventHandler(DoMoreTabControl_DrawItem);
Invalidate();
}
}
However, the dotted rectangle still appears.
I also tried overriding the MyTabControl.OnPaint() method but it doesn't help.
Is there any way to achieve this?
Set the focus to tab instead of header (like this)
private void tabControl1_Click(object sender, EventArgs e)
{
(sender as TabControl).SelectedTab.Focus();
}
You will see dotted rectangle for a millisecond, as soon as the above event gets executed it will disappear.
Also, to remove dotted rectangle for default selected tab on load
private void tabControl1_Enter(object sender, EventArgs e)
{
(sender as TabControl).SelectedTab.Focus();
}
Both this changes worked for me!
hope it helps somebody.
Yes, DrawItem event. You didn't post it, impossible to guess what's wrong with it. Just make sure that you don't call e.DrawFocusRectangle(), likely to present when you copied the MSDN sample code. Simply deleting the statement is sufficient. Consider using a different background color or text font style as an alternative so the focus hint isn't entirely lost.
I'm implementing a custom control that inherits from Control. I want it to be focusable (it's a kind of list box).
In the constructor, I do
SetStyle(ControlStyles.Selectable, true);
I can now use Tab to navigate to the control.
However, when the control receives a mouse click, it does not automatically claim focus. I can work around this, of course:
protected override void OnMouseDown(MouseEventArgs e)
{
Focus();
base.OnMouseDown(e);
}
But this feels like a kludge that should not be necessary. Is this really the way to go? Or is there some way to tell Control to claim focus automatically when it receives a mouse click?
Disassembly to the rescue! It turns out that
SetStyle(ControlStyles.UserMouse, true);
does the trick.
Ironically, I had read in the documentation:
UserMouse: If true, the control does its own mouse processing, and mouse events are not handled by the operating system.
That seemed the exact opposite of what I wanted, so I had only tried setting it to false... Way to go, WinForms documentation.
Yes, that what you should do. There are many controls that don't have a meaningful way to take the focus. PictureBox, Panel are good examples. Anything that derives from ContainerControl. Control.OnMouseDown() therefore doesn't automatically call Focus() in OnMouseDown().
Just overriding the OnMouseDown method isn't enough, you should also make it clear to the user that your control has the focus. So she'll have an idea where the keyboard strokes go. That requires overriding OnPaint() so you can draw a focus rectangle. ControlPaint.DrawFocusRectangle() is a boilerplate implementation for that.
But taking the focus is really only useful if you do something meaningful with keyboard messages. So you'll have to override OnKeyDown and/or OnKeyPressed as well. And show feedback to the user so she can see what she typed. If you don't have a useful implementation for that, you shouldn't take the focus. Which is why PictureBox doesn't.
compile in a WinForms project for FrameWork 3.5
drag an instance of Control1 from the ToolBox to a Form Surface ... make sure its TabStop property is set to 'true
put some other controls on the form.
verify that when the instance of Control1 is tabbed to: it shows a selection rectangle which disappears as you "tab away" from it.
verify if you click on the instance of Control1 that it shows the selection rectangle, and if you click on some other control it disappears.
namespace testFocusableControl
{
// VS Studio 2010 RC1 : Tested against FrameWork 3.5 Full (not 'Client)
public class Control1 : Control
{
public Control1()
{
SetStyle(ControlStyles.UserMouse, true);
}
protected override void OnLostFocus(EventArgs e)
{
this.Invalidate();
base.OnLostFocus(e);
}
protected override void OnGotFocus(EventArgs e)
{
this.Invalidate();
base.OnGotFocus(e);
}
protected override void OnPaint(PaintEventArgs e)
{
if (this.Focused)
{
ControlPaint.DrawFocusRectangle(e.Graphics, this.ClientRectangle, Color.Red, Color.Blue);
}
base.OnPaint(e);
}
}
}
The only "loose end" here for me is that this solution will show the selection rectangle on a mouse-click, but I did not implement any MouseDown code as Thomas suggested.
Note that if you make the Control above a 'ContainerControl via 'SetStyle(ControlStyles.ContainerControl, true); and add some other control to it, even if you set the TabStop property of the added control to 'false : ... if it clicked ... it will get focus, and you will lose the focus rectangle shown on the ContainerControl.