I used this code to implement on hover tooltip, it works with TextBox, ComboBox, MaskedTextBox but not on NumericUpDown. Does anybody know why it is not working?
public static void addHovertip(ToolStripStatusLabel lb, Control c, string tip)
{
c.MouseEnter += (sender, e) =>
{
lb.Text = tip;
// MessageBox.Show(c.Name);
};
c.MouseLeave += (sender, e) =>
{
lb.Text = "";
};
}
I admit the deleted answer from Hans Passant helped a bit in creating this answer.
First of all your code works fine. If you're working with events that happen rather often (like the MouseEvents) you better add a Debug.WriteLine to your code so you can see in the Debugger Output Window which events, for which controls, in which order take place.
The main problem is that due to the fact that the numeric up/down control is a control that is buildup with two different child controls your MouseLeave event is called as soon as the mouse enters one of the two child controls. What happens is: MouseEnter is called when the mouse hits the single line border of the control and MouseLeave is called the moment the mouse is no longer on that line. In MouseLeave you set the Label to an emtpy string. That gives the impression that your code doesn't work.
By simply adding a loop to go over any child controls solves this issue. This still set the label to an empty string a bit to often but it is also immediately set to the correct text if needed.
Here is the changed code with the Debug statements in place.
public static void addHovertip(ToolStripStatusLabel lb, Control c, string tip)
{
c.MouseEnter += (sender, e) =>
{
Debug.WriteLine(String.Format("enter {0}", c));
lb.Text = tip;
};
c.MouseLeave += (sender, e) =>
{
Debug.WriteLine(String.Format("Leave {0}", c));
lb.Text = "";
};
// iterate over any child controls
foreach(Control child in c.Controls)
{
// and add the hover tip on
// those childs as well
addHovertip(lb, child, tip);
}
}
For completeness here is the Load event of my test form:
private void Form1_Load(object sender, EventArgs e)
{
addHovertip((ToolStripStatusLabel) statusStrip1.Items[0], this.numericUpDown1, "fubar");
}
Here is an animated gif demonstrating what happens when you move the mouse in and out the Numeric Up Down control:
Related
I have a RichTextBox which is in a page of my TabControl. Take notice that the RichTextBox is programmaticaly made with the following code:
TabPage addedTabPage = new TabPage("Tab Page");
tabControl.TabPages.Add(addedTabPage);
RichTextBox addedRichTextBox = new RichTextBox()
{
Parent = addedTabPage,
Dock = DockStyle.Fill,
/* ... */
};
Moreover, at the beginning of each of my methods I use this code in order to have access to my RichTextBox:
RichTextBox programTextBox =
(RichTextBox)tabControl.TabPages[tabControl.SelectedIndex].Controls[0];
Everything in my program seems to work fine, but I've noticed that the Text_Changed event is never fired. Why does that happen and how can make this event fire (preferably by its own as it does in a simple RichTextBox).
addedRichTextBox.TextChanged += addedRichTextBox_TextChanged;
void addedRichTextBox_TextChanged(object sender, EventArgs e)
{
MessageBox.Show("Text changed");
}
Refer to this previous question I asked where I couldn't get the click event for a UserControl on my form to fire off.
The way I have my control set up is I have the control itself sized to 50, 20. I then a have panel sized 25, 20 set to dock on the right side. In code within the UserControl itself, anytime the background of the control or the panel that acts as the "switch" are clicked, it fires off this code:
private void toggleClick(object sender, EventArgs e) {
if (toggleStatus) { // set to "on"
this.BackColor = Color.Red;
this.pnlSwitch.Dock = DockStyle.Left;
toggleStatus = false;
} else { // set to "off"
this.BackColor = Color.Green;
this.pnlSwitch.Dock = DockStyle.Right;
toggleStatus = true;
}
}
This works great and goes off every time. However, I put my UserControl in a form and tried to tie a click event to this method:
private void toggleSoundClick(object sender, EventArgs e) {
MessageBox.Show("Test");
}
When I click on the background of the control, this fires off and everything works like it should. However, if I click on the panel that acts as the "switch", the click event in my form doesn't fire (but the click event in the UserControl itself does, which isn't the issue). I can kind of understand this being something like z-indexing in CSS, but it still baffles me why a click event inside the control wouldn't cause it to fire off.
My question is, how can I get around this type of behavior?
edit I also can't integrate the behavior from the click event inside the form into the click event inside the UserControl. I have several of these controls on my form, and all have different behavior depending on which is clicked.
After taking Sinatr's suggestion I googled a bit more and found this post on here. What I ended up doing was unsubscribing all click events for the "switch" panel, then putting this code inside my UserControl.
public new event EventHandler Click {
add {
base.Click += value;
foreach (Control control in Controls) {
control.Click += value;
}
}
remove {
base.Click -= value;
foreach (Control control in Controls) {
control.Click -= value;
}
}
}
This made it so that when I subscribed the click event for the control itself to the toggleClick method, it also registered it with the "switch" panel (which is why I unregistered all other click events, otherwise it would fire off twice), and it also caused the toggleSoundClick method to be subscribed to the "switch" panel as well when I subscribed it to the control itself inside my form.
edit For my purposes I wanted to add the click events recursively to all controls, no matter the depth, so I changed the code to this
public new event EventHandler Click {
add {
subscribeToEvent(this, value);
}
remove {
unsubscribeFromEvent(this, value);
}
}
private void subscribeToEvent(Control control, EventHandler eventHandler) {
control.Click += eventHandler;
foreach (Control child in control.Controls) {
subscribeToEvent(child, eventHandler);
}
}
private void unsubscribeFromEvent(Control control, EventHandler eventHandler) {
control.Click -= eventHandler;
foreach (Control child in control.Controls) {
unsubscribeFromEvent(child, eventHandler);
}
}
In the UserControl code that is firing correctly, add
e.Handled = false;
By setting the EventArgs' Handled property to false, the click event is passed through and hopefully caught (again) by the listener in your form method.
I have a WinForms app that contains many NumericUpDown controls. In a nutshell, if my users enter a value into the control and then delete the text, I want to restore it (the text) when the control loses focus. So I decided that I'd check .Text when the control loses focus and if it's empty, I set .Text = .Value.ToString().
I'm doing this in the Leave event handler and it works just fine. But as I said, I have many of these controls (18, to be exact). I don't like creating 18 Leave event handlers that all do the same thing so I created a generic one like this:
private void numericUpDown_GenericLeave(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(((NumericUpDown)sender).Text))
((NumericUpDown)sender).Text = ((NumericUpDown)sender).Value.ToString();
}
I started to hook up all of the controls to this generic event handler but I quickly got tired of doing this:
numericUpDown1.Leave += numericUpDown_GenericLeave;
numericUpDown2.Leave += numericUpDown_GenericLeave;
numericUpDown3.Leave += numericUpDown_GenericLeave;
...
numericUpDown18.Leave += numericUpDown_GenericLeave;
So I thought I'd create a function that would return a list of all the controls of a specified type and then loop through that list and hookup the event handlers. That function looks like this:
public static List<Control> GetControlsOfSpecificType(Control container, Type type)
{
var controls = new List<Control>();
foreach (Control ctrl in container.Controls)
{
if (ctrl.GetType() == type)
controls.Add(ctrl);
controls.AddRange(GetControlsOfSpecificType(ctrl, type));
}
return controls;
}
I call the function like this:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
foreach (var numericUpDownControl in listOfControls)
{
numericUpDownControl.Leave += numericUpDown_GenericLeave;
}
When I run my app, however, I don't see the expected behavior that occurs when I manually hookup each control to the generic event handler. This code is currently in the constructor of my form and I've tried calling it before as well as after the call to InitializeComponent() but neither one seems to be working. I get no error of any kind, I just don't see the behavior that I was expecting. I have a breakpoint set inside the generic event handler but the debugger never breaks so it seems like the event handler isn't being hooked up correctly. Does anyone know why this might be or how I can troubleshoot it further? Thanks!
EDIT
I just realized that the call to:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
was happening before the call to InitializeComponent() so of course the list of controls being returned was empty. DOH! Thanks for all the replys. I apologize for wasting everyones time. :-(
You're passing this to your method, which is presumably a reference to your form. Your method will only catch the controls that are placed directly on your form. Any NumericUpDown controls that are not directly on the form (i.e. they're sitting on a panel or something) will be missed.
Why not create a user control that has a NumericUpDown control in it.
Then handle this is in the user control events.
This worked for me:
private decimal _previous = 0;
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
if (((NumericUpDown)sender).Text.Length > 0)
{
_previous = this.numericUpDown1.Value;
}
}
private void UserControl1_Leave(object sender, EventArgs e)
{
if (this.numericUpDown1.Text == "")
{
this.numericUpDown1.Value = _previous;
this.numericUpDown1.Text = System.Convert.ToString(_previous);
}
}
Just note that the Leave event is on the user control not on the updown control itself.
Question answered. See Edit above. Thanks to bsegraves for pointing me in the right direction.
I am trying to make a character counter, 40/200...41/200 and so on. Now for a textbox control I am hooking into the KeyUp Event with something like this...
public static void GetRemainingChars(MyTextBox txt, LabelControl lbl)
{
var maxChars = txt.Properties.MaxLength;
lbl.Text = txt.Text.Length + "/" + maxChars;
}
Unfortunately the MemoExEdit control has a popup window you type the text into and that seems to be hidden. I tried the KeyUp, EditValueChanging, TextChanged, and they all do the same thing. They don't fire till the user closes the popup. I am guessing that it is a composite control that transfers the editvalue when it closes.
Any ideas on how I can get at the popups events? Is there a different way to do this?
Just because I couldn't find this anywhere else I will post my solution for other's benefit.
Subscribe to the Popup Event of the MemoExEdit control, then inside that subscribe to the EditValueChanging Event. That is where you can hook in. See below for MY working version. Tweaks may be needed for yourself. Also, the Popup Event is created in my Designer.cs file.
private void memContactWith_Properties_Popup(object sender, EventArgs e)
{
MemoExPopupForm popupForm = (sender as DevExpress.Utils.Win.IPopupControl).PopupWindow as MemoExPopupForm;
MemoEdit me = popupForm.Controls[2] as MemoEdit;
me.EditValueChanging += new DevExpress.XtraEditors.Controls.ChangingEventHandler(me_EditValueChanging);
}
void me_EditValueChanging(object sender, DevExpress.XtraEditors.Controls.ChangingEventArgs e)
{
var memo = (sender as MemoEdit);
var maxChars = memo.Properties.MaxLength;
lblContactWithCharCount.Text = memo.Text.Length + "/" + maxChars;
}
As long as the mouse is over a specific control, we show some form. When the mouse leaves the control, we hide the control after a small timeout. This is standard hover behavior.
However, when a control (for example a Treeview) has a scrollbar, and the mouse is ON or OVER the scrollbar, the events don't fire ...
If we could get a reference to the scrollbar control, this would solve our problem, as we would add the same listener events to the scrollbar. However, the scrollbar isn't accessible as far as I know ...
How can we solve this problem ?
The scrollbar is in the tree view's non-client area. When the mouse moves there, it starts generating non-client messages like WM_NCMOUSEMOVE and WM_NCMOUSELEAVE. You would have to sub-class the TreeView and override WndProc() to detect these message.
This doesn't really solve your problem though, you'll still have a hard time with edge cases. A low-tech approach with a Timer always works:
private Form frmPopup;
private void treeView1_MouseEnter(object sender, EventArgs e) {
timer1.Enabled = true;
if (frmPopup == null) {
frmPopup = new Form2();
frmPopup.StartPosition = FormStartPosition.Manual;
frmPopup.Location = PointToScreen(new Point(treeView1.Right + 20, treeView1.Top));
frmPopup.FormClosed += (o, ea) => frmPopup = null;
frmPopup.Show();
}
}
private void timer1_Tick(object sender, EventArgs e) {
Rectangle rc = treeView1.RectangleToScreen(new Rectangle(0, 0, treeView1.Width, treeView1.Height));
if (!rc.Contains(Control.MousePosition)) {
timer1.Enabled = false;
if (frmPopup != null) frmPopup.Close();
}
}
I think there are several different ways to do this, but the key is your desire to have a timeout on the action. I think a combination of two techniques might work:
Put the control on a panel, docked to fill, and use the MouseEnter of the panel to turn on your behavior -- this will include the control's scrollbar. You can use the MouseLeave event of the panel as well, but you'll have to check the cursor's position to ensure it hasn't moved into the contained control. This method is mostly reliable, but moving the mouse quickly can confuse it.
If you combine this with a timer that starts when your shown/hidden control is shown and check the cursor position periodically. This will work, but your timeout before hiding the control won't necessarily be consistent (because the timer starts when they enter the control). You could stop/start the timer on mousemoves in the control to alleviate this somewhat.
I put together a project of the different methods I tried here: http://lovethedot.s3.amazonaws.com/100609StackoverflowScrollbarQuestion.zip
By docking the control you want to track in the panel, it essentially wraps it and you'll get MouseEnter at the very edge of the tracked control:
private void panel1_MouseEnter(object sender, EventArgs e)
{
this.Text = "in";
}
private void panel1_MouseLeave(object sender, EventArgs e)
{
if (!new Rectangle(new Point(0, 0), panel1.Size).Contains(panel1.PointToClient(Control.MousePosition)))
this.Text = "out";
}
You're tracking entry into the panel surrounding the control, and exit from that panel provided the cursor isn't inside the tracked control.
To get a better "leave" experience, it's combined with a Timer that checks to see where the cursor is as well:
private void listBox3_MouseEnter(object sender, EventArgs e)
{
button1.Visible = true;
visibleTimer.Stop();
visibleTimer.Start();
}
void visibleTimer_Tick(object sender, EventArgs e)
{
if (!new Rectangle(new Point(0, 0), listBox3.Size).Contains(listBox3.PointToClient(Control.MousePosition)))
{
visibleTimer.Stop();
button1.Visible = false;
}
}