5,663,486 members and growing! (17,414 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, C#, Windows, .NET, .NET 2.0VS2005, Visual Studio, Dev

Posted: 10 Jan 2007
Updated: 10 Jan 2007
Views: 13,214
Bookmarked: 14 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
9 votes for this Article.
Popularity: 3.14 Rating: 3.29 out of 5
1 vote, 11.1%
1
1 vote, 11.1%
2
0 votes, 0.0%
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



Location: Canada Canada

Other popular .NET Framework articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 10 of 10 (Total in Forum: 10) (Refresh)FirstPrevNext
GeneralAutoPopDelay [modified]memberDaniel B (u.s.)14:12 15 Jul '08  
GeneralRe: AutoPopDelaymemberMember 139626911:37 18 Jul '08  
GeneralRe: AutoPopDelaymemberDaniel B (u.s.)12:39 31 Jul '08  
GeneralA Big Help to mememberaggallentes23:56 25 Nov '07  
GeneralA fix for the flickering of the ErrorProvider Iconsmemberdushafer5:55 20 Sep '07  
GeneralA Simple Fixmemberthe larker17:07 24 Aug '07  
GeneralRe: A Simple FixmemberDunceantix5:58 28 Aug '07  
GeneralOther strange behaviormemberLarryFix7:44 9 Mar '07  
GeneralSubsitute of timer.membercodebased1:55 19 Feb '07  
GeneralUseful tipmemberseh036714: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-2008
Web11 | Advertise on the Code Project