Click here to Skip to main content
6,291,522 members and growing! (10,767 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » .NET Framework » General     Intermediate License: The Code Project Open License (CPOL)

ErrorProvider - Fix for Disappearing ToolTip

By Kevin.Molyneaux

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.
C# 2.0, Windows, .NET 2.0VS2005, Dev
Posted:10 Jan 2007
Views:18,108
Bookmarked:17 times
Announcements
Loading...
 
Search    
Advanced Search
printPrint   Broken Article?Report       add Share
  Discuss Discuss   Recommend Article Email
9 votes for this article.
Popularity: 3.14 Rating: 3.29 out of 5
1 vote, 11.1%
1
1 vote, 11.1%
2

3
5 votes, 55.6%
4
2 votes, 22.2%
5
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

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)

About the Author

Kevin.Molyneaux


Member

Location: Canada Canada

Other popular .NET Framework articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 11 of 11 (Total in Forum: 11) (Refresh)FirstPrevNext
GeneralMy easy solution PinmemberEugen Wiebe1:52 19 Mar '09  
GeneralAutoPopDelay [modified] PinmemberDaniel B (u.s.)14:12 15 Jul '08  
GeneralRe: AutoPopDelay PinmemberMember 139626911:37 18 Jul '08  
GeneralRe: AutoPopDelay PinmemberDaniel B (u.s.)12:39 31 Jul '08  
GeneralA Big Help to me Pinmemberaggallentes23:56 25 Nov '07  
GeneralA fix for the flickering of the ErrorProvider Icons Pinmemberdushafer5:55 20 Sep '07  
GeneralA Simple Fix Pinmemberthe larker17:07 24 Aug '07  
GeneralRe: A Simple Fix PinmemberDunceantix5:58 28 Aug '07  
GeneralOther strange behavior PinmemberLarryFix7:44 9 Mar '07  
GeneralSubsitute of timer. Pinmembercodebased1:55 19 Feb '07  
GeneralUseful tip Pinmemberseh036714:43 10 Jan '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 10 Jan 2007
Editor: Deeksha Shenoy
Copyright 2007 by Kevin.Molyneaux
Everything else Copyright © CodeProject, 1999-2009
Web19 | Advertise on the Code Project