Displaying tooltip over a disabled control

Viewed 48643

I'm trying to display a tooltip when mouse hovers over a disabled control. Since a disabled control does not handle any events, I have to do that in the parent form. I chose to do this by handling the MouseMove event in the parent form. Here's the code that does the job:

    void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        m_toolTips.SetToolTip(this, "testing tooltip on " + DateTime.Now.ToString());
        string tipText = this.m_toolTips.GetToolTip(this);
        if ((tipText != null) && (tipText.Length > 0))
        {
            Point clientLoc = this.PointToClient(Cursor.Position);
            Control child = this.GetChildAtPoint(clientLoc);
            if (child != null && child.Enabled == false)
            {
                m_toolTips.ToolTipTitle = "MouseHover On Disabled Control";
                m_toolTips.Show(tipText, this, 10000);
            }
            else
            {
                m_toolTips.ToolTipTitle = "MouseHover Triggerd";
                m_toolTips.Show(tipText, this, 3000);
            }
        }
    }

The code does handles the tooltip display for the disabled control. The problem is that when mouse hovers over a disabled control, the tooltip keeps closing and redisplay again. From the display time I added in the tooltip, when mouse is above the parent form, the MouseMove event gets called roughly every 3 seconds, so the tooltip gets updated every 3 seconds. But when mouse is over a disabled control, the tooltip refreshes every 1 second. Also, when tooltip refreshes above form, only the text gets updated with a brief flash. But when tooltip refreshes above a disabled control, the tooltip windows closes as if mouse is moving into a enabled control and the tooltip is supposed to be closed. but then the tooltip reappears right away.

Can someone tell me why is this? Thanks.

10 Answers

In case of TextBox control, making it as readonly solved the issue.

Since no one ever pointed this out, this works for any control that exposes ToolTipService:

ToolTipService.ShowOnDisabled="True"

As in this example:

<Button Content="OK"
        ToolTipService.ShowOnDisabled="True" />
/*
Inspired by the suggestions above in this post, i wrapped it up as an extended ToolTip control specially works for disabled control. 

                // Reference example
                var td = new ToolTipOnDisabledControl();
                this.checkEdit3.Enabled = false;
                td.SetTooltip(this.checkEdit3, "tooltip for disabled 3333333333333");
*/    

using System;
using System.Windows.Forms;

namespace TestApp1
{
    public class ToolTipOnDisabledControl
    {
        #region Fields and Properties

        private Control enabledParentControl;

        private bool isShown;

        public Control TargetControl { get; private set; }

        public string TooltipText { get; private set; }
        public ToolTip ToolTip { get; }
        #endregion

        #region Public Methods
        public ToolTipOnDisabledControl()
        {
            this.ToolTip = new ToolTip();
        }

        public void SetToolTip(Control targetControl, string tooltipText = null)
        {
            this.TargetControl = targetControl;
            if (string.IsNullOrEmpty(tooltipText))
            {
                this.TooltipText = this.ToolTip.GetToolTip(targetControl);
            }
            else
            {
                this.TooltipText = tooltipText;
            }

            if (targetControl.Enabled)
            {
                this.enabledParentControl = null;
                this.isShown = false;
                this.ToolTip.SetToolTip(this.TargetControl, this.TooltipText);
                return;
            }

            this.enabledParentControl = targetControl.Parent;
            while (!this.enabledParentControl.Enabled && this.enabledParentControl.Parent != null)
            {
                this.enabledParentControl = this.enabledParentControl.Parent;
            }

            if (!this.enabledParentControl.Enabled)
            {
                throw new Exception("Failed to set tool tip because failed to find an enabled parent control.");
            }

            this.enabledParentControl.MouseMove += this.EnabledParentControl_MouseMove;
            this.TargetControl.EnabledChanged += this.TargetControl_EnabledChanged;
        }

        public void Reset()
        {
            if (this.TargetControl != null)
            {
                this.ToolTip.Hide(this.TargetControl);
                this.TargetControl.EnabledChanged -= this.TargetControl_EnabledChanged;
                this.TargetControl = null;
            }

            if (this.enabledParentControl != null)
            {
                this.enabledParentControl.MouseMove -= this.EnabledParentControl_MouseMove;
                this.enabledParentControl = null;
            }

            this.isShown = false;
        }
        #endregion

        #region Private Methods
        private void EnabledParentControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Location.X >= this.TargetControl.Left &&
                e.Location.X <= this.TargetControl.Right &&
                e.Location.Y >= this.TargetControl.Top &&
                e.Location.Y <= this.TargetControl.Bottom)
            {
                if (!this.isShown)
                {
                    this.ToolTip.Show(this.TooltipText, this.TargetControl, this.TargetControl.Width / 2, this.TargetControl.Height / 2, this.ToolTip.AutoPopDelay);
                    this.isShown = true;
                }
            }
            else
            {
                this.ToolTip.Hide(this.TargetControl);
                this.isShown = false;
            }
        }

        private void TargetControl_EnabledChanged(object sender, EventArgs e)
        {
            if (TargetControl.Enabled)
            {
                TargetControl.EnabledChanged -= TargetControl_EnabledChanged;
                enabledParentControl.MouseMove -= EnabledParentControl_MouseMove;
            }
        }
        #endregion
    }
}

I found Serge_Yubenko's code worked on disabled buttons , but in order to stop the flashing make sure the tooltip pops up away from the button - just don't position it half way down the control but do this:

mFormTips.Show(toolTipString, control, control.Width / 2, control.Height);

instead of mFormTips.Show(toolTipString, control, control.Width / 2, control.Height / 2);

This seems to follow the usual tooltip placement too...

So, I came across this post in my efforts to do the same thing, being the top result on Google. I had already considered the mouse move event and while the answers here did help, they didn't provide me with exactly what I wanted - that being a perfect recreation of the original show tooltip event.

The problem I discovered was this: For whatever reason in the API, ToolTip.Show turns the Mouse Move Event into effectively a Mouse Hover Event. Which is why the tooltip keeps flashing.

The workaround as suggested was to keep the tooltip on always show, or to display the tooltip away from the control, but that wouldn't be a faithful recreation, from the show to the timed fade. The answer would suggest that a block to prevent further execution of the code is needed - the reality was 2 blocks in the event code (One of which has no earthly reason existing and yet without it a timed event fires twice ***), a double delclaration of the control location, one inside the event, one class wide, and another class wide to check if the mouse is over a control, a class wide timer, and a Mouse Leave event to clean up due to too fast mouse movement away from the panel housing the control.

As you will see there are two events on the timer, both functions for them are in the event code as they need to reference variables get/set in the code. They can be moved out, but would then need class wide declarations on the variables, and they cause no harm where they are. FYI: "ToolTips" in the code is referencing the ToolTip control I have on the form.

*** Just to expand. If you look at the code you'll see that IsTipReset could be replaced with IsShown - after all they end up at the same value as each other. The reason for IsTipRest is this: If IsShown is used then while moving the mouse inside the control while the tootip is showing will cause a slight hiccup when the tooltip fades and very very very briefly another tooltip will popup. Using IsTipReset stops that. I have no idea why and maybe someone will spot it because I sure can't! Lol.

This is my first post here, and I realise it is an old thread, but I just wanted to share the fruits of my labour. As I said, my goal was a faithful recreation of tooltip and I think I achieved it. Enjoy!

using Timer = System.Windows.Forms.Timer;
private readonly Timer MouseTimer = new();
private Control? Ctrl;
private bool IsControl = false;

private void TopMenuMouseMove (object sender, MouseEventArgs e) {
    Panel Pnl = (Panel)sender;
    Control Area = Pnl.GetChildAtPoint (e.Location);
    bool IsShown = false;
    bool IsTipReset = false;
    if (Area != null && Area.Enabled == false && Area.Visible == true) {
        Ctrl = Pnl.GetChildAtPoint (e.Location);
        Point Position = e.Location;

        if (IsControl) { IsShown = true; } else if (!IsControl) { IsControl = true; IsShown = false; }

        if (!IsShown) {
            MouseTimer.Interval = ToolTips.InitialDelay;
            MouseTimer.Tick += new EventHandler (TimerToolTipShow!);
            MouseTimer.Start ();
        }

        void TimerToolTipShow (object sender, EventArgs e) {
            if (!IsTipReset) {
                MouseTimer.Dispose ();
                string Txt = ToolTips.GetToolTip (Ctrl) + " (Disabled)";
                Position.Offset (-Ctrl.Left, 16);
                ToolTips.Show (Txt, Ctrl, Position);
                MouseTimer.Interval = ToolTips.AutoPopDelay;
                MouseTimer.Tick += new EventHandler (TimerToolTipReset!);
                MouseTimer.Start ();
                IsShown = true;
                IsTipReset = true;
            }
        }

        void TimerToolTipReset (object sender, EventArgs e) {
            if (IsShown) {
                MouseTimer.Dispose ();
                IsShown = false;
                ToolTips.Hide (Ctrl);
            }
        }

    }
    else if (Area == null) {
        if (Ctrl != null) {
            MouseTimer.Dispose ();
            IsShown = false;
            IsControl = false;
            ToolTips.Hide (Ctrl);
            Ctrl = null;
        }
    }
}

private void TopMenuMouseLeave (object sender, EventArgs e) {
    if (Ctrl != null) {
        MouseTimer.Dispose ();
        IsControl = false;
        ToolTips.Hide (Ctrl);
        Ctrl = null;
    }
}
Related