Click here to Skip to main content
15,891,136 members
Articles / Programming Languages / C#
Article

ErrorProvider - Fix for Disappearing ToolTip

Rate me:
Please Sign up or sign in to vote.
3.61/5 (11 votes)
10 Jan 2007CPOL1 min read 66K   410   22   14
Microsoft's ToolTips have a 'feature' that when you click on a tooltip, the tooltip goes away and does not reappear, which is a major problem with the ErrorProvider messages. This posting presents a hacked solution; I am posting this in hopes that it will bring about a better resolution.
Sample image

Introduction

To better understand the problem, run the demo app included as a download to this post. When you hover over the Microsoft error provider, the error message is displayed. Next, click the mouse and the error message goes away. When you move the mouse off the error provider icon and return it will not re-appear.

I have discovered that setting a control's error message to "" and then resetting it, will allow the error message to be re-displayed (the trick*).

The ErrorProviderFixed implementation, which follows, is a simple extension of the ErrorProvider. It looks at the ErrorProvider’s private member Windows and registers it with our own NativeWindow implementation, allowing us monitor its WinProc messages. When we need to, we reset the error provider messages (re: *the trick).

Note: “*the trick” as explained above re-enables the messages so that they display, but causes a slight flicker. I would much prefer it if Microsoft could fix this bug.

Usage

To replace the Microsoft ErrorProvider with ErrorProviderFixed, all you need to do is include the following code in your project in a file such as ErrorProviderFixed.cs.

Then, replace all instances of System.Windows.Forms.ErrorProvider with ErrorProviderFixed.

ErrorProviderFixed Code

C#
public class ErrorProviderFixed : ErrorProvider
    {
        ErrorProviderFixManager mToolTipFix = null;
 
        public ErrorProviderFixed()
            : base()
        {
            mToolTipFix = new ErrorProviderFixManager(this);
        }
 
        public ErrorProviderFixed(ContainerControl parentControl)
            : base(parentControl)
        {
            mToolTipFix = new ErrorProviderFixManager(this);
        }
 
        public ErrorProviderFixed(IContainer container)
            : base(container)
        {
            mToolTipFix = new ErrorProviderFixManager(this);
        }
 
        protected override void Dispose(bool disposing)
        {
            mToolTipFix.Dispose();
            base.Dispose(disposing);
        }
    }
 
    public class ErrorProviderFixManager : IDisposable
    {
        private ErrorProvider mTheErrorProvider = null;
        private Timer mTmrCheckHandelsProc = null;
        private Hashtable mHashOfNativeWindows = new Hashtable();
 
        public void Dispose()
        {
            mTmrCheckHandelsProc.Stop();
            mTmrCheckHandelsProc.Dispose();
            mTmrCheckHandelsProc = null;
        }
 
        /// <SUMMARY>
        /// constructor, which will started a timer that will
        /// keep the errorProviders tooltip window up-to-date and enabled.
        ///
        /// To do: I would like to do this without a timer (Suggestions welcome).
        /// </SUMMARY>
        /// <param name="ep"></param>
        public ErrorProviderFixManager(ErrorProvider ep)
        {
            mTheErrorProvider = ep;
 
            mTmrCheckHandelsProc = new Timer();
            mTmrCheckHandelsProc.Enabled = true;
            mTmrCheckHandelsProc.Interval = 1000;
            mTmrCheckHandelsProc.Tick += new EventHandler(tmr_CheckHandels);
        }
 
        /// <SUMMARY>
        /// Resets the error provider, error messages.  I've noticed that when
        /// you click on an error provider, while its tooltip is displayed,
        /// the tooltip doesn't return.  It will return if the text is reset, or
        /// if the user is able to hover over another error provider message for
        /// that errorProvider instance.
        /// 
        /// Todo: I would like to find an easier way to fix this...
        /// Email me at: km@KevinMPhoto.com
        /// </SUMMARY>
        private void RefreshProviderErrors()
        {
            Hashtable hashRes = (Hashtable)GetFieldValue(mTheErrorProvider, "items");
            foreach (Control control in hashRes.Keys)
            {
                if (hashRes[control] == null)
                {
                    break;
                }
 
                if (!(bool)GetFieldValue(hashRes[control], "toolTipShown"))
                {
                    if (mTheErrorProvider.GetError(control) != null && 
                        mTheErrorProvider.GetError(control).Length > 0)
                    {
                        string str = mTheErrorProvider.GetError(control);
                        ErrorBlinkStyle prev = mTheErrorProvider.BlinkStyle;
                        mTheErrorProvider.BlinkStyle = ErrorBlinkStyle.NeverBlink;
                        mTheErrorProvider.SetError(control, "");
                        mTheErrorProvider.SetError(control, str);
                        mTheErrorProvider.BlinkStyle = prev;
                    }
                }
            }
        }
 
        /// <SUMMARY>
        /// This method checks to see if the error provider's tooltip window has
        /// changed and if it updates this Native window with the new handle.
        /// 
        /// If there is some sort of change, it also calls the RefreshProviderErrors, 
        /// which fixes the tooltip problem...
        /// </SUMMARY>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void tmr_CheckHandels(object sender, EventArgs e)
        {
if (mTheErrorProvider.ContainerControl == null)
            {
                return;
            }
 
            if (mTheErrorProvider.ContainerControl.Visible)
            {
                Hashtable hashRes = 
                     (Hashtable)GetFieldValue(mTheErrorProvider, "windows");
                if (hashRes.Count > 0)
                {
                    foreach (Object obj in hashRes.Keys)
                    {
                        ErrorProviderNativeWindowHook hook = null;
                        if (mHashOfNativeWindows.Contains(obj))
                        {
                            hook = (ErrorProviderNativeWindowHook)
                                                  mHashOfNativeWindows[obj];
                        }
                        else
                        {
                            hook = new ErrorProviderNativeWindowHook();
                            mHashOfNativeWindows[obj] = hook;
                        }
 
                        NativeWindow nativeWindow = GetFieldValue(hashRes[obj], 
                                                    "tipWindow") as NativeWindow;
                        if (nativeWindow != null && hook.Handle == IntPtr.Zero)
                        {
                            hook.AssignHandle(nativeWindow.Handle);
                        }
                    }
                }
 
                foreach (ErrorProviderNativeWindowHook hook 
                           in mHashOfNativeWindows.Values)
                {
                    if (hook.mBlnTrigerRefresh)
                    {
                        hook.mBlnTrigerRefresh = false;
                        RefreshProviderErrors();
                    }
                }
            }
        }
 
        /// <SUMMARY>
        /// A helper method, which allows us to get the value of private fields.
        /// </SUMMARY>
        /// <param name="instance">the object instance</param>
        /// <param name="name">the name of the field, which we want to get</param>
        /// <RETURNS>the value of the private field</RETURNS>
        private object GetFieldValue(object instance, string name)
        {
            if (instance == null) return null;
 
            FieldInfo fInfo = null;
            Type type = instance.GetType();
            while (fInfo == null && type != null)
            {
                fInfo = type.GetField(name, 
                       System.Reflection.BindingFlags.FlattenHierarchy | 
                       System.Reflection.BindingFlags.NonPublic | 
                       BindingFlags.Instance);
                type = type.BaseType;
            }
            if (fInfo == null)
            {
            }
            return fInfo.GetValue(instance);
        }
    }
 
    /// <SUMMARY>
    /// A NativeWindow, which we use to trap the WndProc messages
    /// and patch up the ErrorProvider/ToolTip bug.
    /// </SUMMARY>
    public class ErrorProviderNativeWindowHook : NativeWindow
    {
        private int mInt407Count = 0;
        internal bool mBlnTrigerRefresh = false;
 
        /// <SUMMARY>
        /// This is the magic.  On the 0x407 message, we need to reset the 
        /// error provider; however, I can't do it directly in the WndProc; 
        /// otherwise, we could get a cross threading type exception, since
        /// this WndProc is called on a separate thread.  The Timer will make
        /// sure the work gets done on the Main GUI thread.          
        /// </SUMMARY>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x407)
            {
                mInt407Count++;
                if (mInt407Count > 3)  // if this occurs we need to release...
                {
                    this.ReleaseHandle();
                    mBlnTrigerRefresh = true;
                }
            }
            else
            {
                mInt407Count = 0;
            }
 
            base.WndProc(ref m);
        }
    }
}

History

  • 10th January, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralVery Simple Fix Pin
Ned Ames23-Oct-09 5:31
Ned Ames23-Oct-09 5:31 
GeneralImprovement Pin
Nachman25-Aug-09 19:43
Nachman25-Aug-09 19:43 
GeneralMy easy solution Pin
Eugen Wiebe19-Mar-09 0:52
Eugen Wiebe19-Mar-09 0:52 
GeneralAutoPopDelay [modified] Pin
Daniel B (u.s.)15-Jul-08 13:12
Daniel B (u.s.)15-Jul-08 13:12 
GeneralRe: AutoPopDelay Pin
st0icsmitty18-Jul-08 10:37
st0icsmitty18-Jul-08 10:37 
GeneralRe: AutoPopDelay Pin
Daniel B (u.s.)31-Jul-08 11:39
Daniel B (u.s.)31-Jul-08 11:39 
GeneralA Big Help to me Pin
aggallentes25-Nov-07 22:56
aggallentes25-Nov-07 22:56 
GeneralA fix for the flickering of the ErrorProvider Icons Pin
dushafer20-Sep-07 4:55
dushafer20-Sep-07 4:55 
GeneralA Simple Fix Pin
the larker124-Aug-07 6:07
the larker124-Aug-07 6:07 
GeneralRe: A Simple Fix Pin
Dunceantix28-Aug-07 4:58
Dunceantix28-Aug-07 4:58 
GeneralOther strange behavior Pin
LarryFix9-Mar-07 6:44
LarryFix9-Mar-07 6:44 
GeneralRe: Other strange behavior Pin
Periyar Selvam J19-Nov-15 13:01
Periyar Selvam J19-Nov-15 13:01 
GeneralSubsitute of timer. Pin
codebased19-Feb-07 0:55
codebased19-Feb-07 0:55 
GeneralUseful tip Pin
seh036710-Jan-07 13:43
seh036710-Jan-07 13:43 
Just ran into this with a winforms project. Thanks for the help.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.