none
Poor WPF application performance due to UI Automation on certain machines

    Question


  • I am dealing with a performance issue an a WPF application that is related to UI Automation. 

    The symptom:
    On certain machines, expanding some expanders in our app can hang the app for minutes on end.  On other machines this operation is effectively instantaneous.

    Attaching a debugger to the process when it is apparently hung reveals that WPF is executing deep recursive calls to UIElementHelper.InvalidateAutomationAncestors. Poking around in Reflector shows that UIElement.Measure will trigger a call to InvalidateAutomationAncestors if ContextLayoutManager.From(base.Dispatcher).AutomationEvents.Count != 0.  After setting some breakpoints when automation events are added shows that these AutomationEvents are added via the WPF toolip popup plumbing, when there are global Win32 event hooks registered for certain events.

    So I guess my question boils down to:

    1) Is there a way to protect my application so that UI Automation doesn't destroy its peformance? On the machine I was testing, it turned out that it was Norton Antivirus that had caused the bad interaction, but the number of programs that can trigger this behavior is apparently unbounded.

    2) Could it be that we've somehow built our app such that we are triggering degenerate behavior when UIElementHelper.InvalidateAutomationAncestors runs? Any suggestions on how to identify something we may have done wrong?

    Friday, July 02, 2010 1:51 PM

All replies

  • Hi John,

    We already aware of this issue and we will be fixing this one in the next release ( most likely in .NET 4.0 sp1). In the meantime you could try one of the below workarounds

     

    1. Automation code will be triggered only if there are any automation clients ( like screen reader, tabtip in tablet pcs, etc) running in the machine. So one way to get out of this situation is to close any of those automation client apps.

    2. If one is not feasible then an alternative is,  UIElementHelper.InvalidateAutomationAncestors  will take longer time only if automation tree for the app is sparse ( happens if had disabled building automation tree using custom window automation peer) and visual tree is dense. So another solution is disable any custom automation code and allow WPF to build complete automation tree. This should speed up UIElementHelper.InvalidateAutomationAncestors as well.

    Let me know how this works.

    Thanks,

    Rath

     

     

    Friday, July 09, 2010 10:11 PM
  • Thanks for the response.

    Unfortunately solution 1 is probably not feasible, since our app will be deployed via ClickOnce into an unpredicatable environment.

    Regarding solution 2, we haven't done anything intentionally that might have made the automation tree 'sparse'.  Facilitating automation has not been a design goal, and there are no custom automation peers in our app.  Are there any other reasons other than custom automation peers that may cause the automation tree to become sparse?

    Thanks,

    John

    Monday, July 12, 2010 12:43 PM
  • Here's another interesting data point that I'm not sure how to interpret.  It turns out that the performance degradation with the expander does not occur if the window transitions to the maximized state (starting the app with the window maximized via XAML is not sufficient) before the expansion.  Also, it seems that it is the transition that helps.  Un-maximizing the window and then expanding the expander seems to behave properly. 

    For now we have a workaround where we maximize the main window immediately after showing it, but that's crufty as heck and I don't have much confidence that there isn't something else that may trigger the degenerate behavior again.

     

    Monday, July 12, 2010 7:40 PM
  • Hi,

    I think I'm dealing with the same issue on a client machine that was previously used for UI testing via Automation code.

    I tried to stop all automation clients, but the problem still occured.  

    It seems to me as if there is still one automation client running ...

    Is there a way to detect which automation client is triggering the automation code?

     

    Regards,

    Pieter

    Thursday, August 19, 2010 6:34 AM
  • Hi Pieter,

    Unfortunately I haven't found any way to identify which application is responsible. I've always had to use a trial and error approach.

    I looked for a Win32 API call that would identify which applications have registered event hooks for a particular events, but I was't able to find one...

    -John

    Thursday, August 19, 2010 2:12 PM
  • Hi,

    I suffered from exactly the same symptoms, and I've managed to find a workaround in the meantime while Microsoft is working on the service pack.

    The problem is not that there are UIAutomation Clients, but that there are NO UIAutomation clients.

    The solution is to simulate a client that does not do anything within the same program you are programming:

     

            void mainWindow_GotFocus(object senderRoutedEventArgs e)
            {
                AutomationElement element = AutomationElement.RootElement;
                Automation.Condition condition = new PropertyCondition(AutomationElement.NameProperty"Doesn't matter");
                AutomationElement automationElement = element.FindFirst(TreeScope.Childrencondition);
                ((Window)sender).GotFocus -= mainWindow_GotFocus;
            }

    include references to UIAutomationClient and UIAutomationTypes and add this:

    using Automation = System.Windows.Automation;
    using System.Windows.Automation;

    Please post if this code helps you too.

    - CheckPoint -

    Tuesday, August 31, 2010 9:33 AM
  • I hope a fix comes out for this sooner than an SP1 because this actually started happening to me while using the Workflow designer in Visual Studio 2010. I thought something was wrong with my Workflow because the UI would hang for minutes and sometimes not come back at all. I eventually attached the debugger to the hung version of Visual Studio and found a huge stack trace filled with InvalidateAutomationAncestors calls which led me to this post.
    Tuesday, August 31, 2010 8:34 PM
  • Wow.

    I hereby nominate Ron for Gold Star of Awesomeness for posting an effective workaround (unlike Pietr it apparently worked for me by attaching the handler to the main window) and MS for the WTF award for forcing developers to get caught up in this sort of nonsense.   I sincerely hope this is addressed in SP1, whenever that appears.

    Monday, September 13, 2010 1:02 PM
  • John,

    The solution (posted by Ron) seemed to work on a first test pc.

    In the meanwhile, we did some further testing (because I was a bit suspicious by the posted fix too) and finally we triggered google desktop search as automation peer.

    I think the application was not running during the tests with Ron's fix. That's why I believed the issue was fixed.
    Appologies for incorrect post.

    (thanks anyway Ron, for your input.)

     I also hope for a fix in SP1 or earlier!

    Monday, September 13, 2010 1:50 PM
  • Thats interesting Pietr that Ron's workaround did not seem to have any effect with Google Desktop in your application.  I was also testing the behavior of my app with Google Desktop, and that's what I was using to determine whether the workaround had any effect. When Google Desktop was running, and without Ron's workaround, I could easily get may app to hang. With the workaround in place, nothing I was previously doing to trigger the hang seemed to have that effect.

    Admittedly I'm still somewhat skeptical, because this reeks of cargo-cult programming to me, but at this point am I cautiously optimistic.

    Monday, September 13, 2010 6:11 PM
  • John,

    As a temp workaround, we clear the AutomationEvents object via reflection in a background worker (check every x sec.). Very wrong, I have to admit, but the user can continue to work with the application!

    We cannot wait till SP1!

    Regards.

    Tuesday, September 14, 2010 6:18 AM
  • We experienced the same problem: our application becomes irresponsive for several minutes. It also has a large visual tree. The debugger showed the deep recursive calls to UIElementHelper.InvalidateAutomationAncestors. The proposed solution of Ron didn't work in our case.

    We found out that some tooltips caused the Automation Events, which caused the deep recursive calls. As a quick fix we removed those tooltips from our application.

    Let's wait for SP 1...

    Monday, September 27, 2010 9:59 AM
  • New and improved Automation Fix.

    Put this code when the program starts, this will work in all situations my previews fix did not work on

                WindowInteropHelper helper = new WindowInteropHelper(mainWindow);
                AutomationElement mainWindowAutomationElement = AutomationElement.FromHandle(helper.Handle);
                Automation.Automation.AddStructureChangedEventHandler(mainWindowAutomationElementTreeScope.DescendantsAutomationFix);

             void AutomationFix(object senderStructureChangedEventArgs e)
    {
                 AutomationElement element = sender as AutomationElement;
    Automation.Condition condition = new PropertyCondition(AutomationElement.NameProperty"!!");
    AutomationElement automationElement = element.FindFirst(TreeScope.Childrencondition);
    }

    In addition we have filed a ticket to microsoft and they should release a real fix sometimes soon.

    Please tell me if this worked better and in all machines.

    Hope this will be better then clearing the AutomationEvents using reflection every second although this may still reduce some performance... BTW, using a narrator with the programs will work too.

    Regards

    Monday, September 27, 2010 11:50 AM
  • Thanks Ron! Your new solution seems to work in our case. This is better than disabling all tooltips.

    We optimized your code as follows (otherwise thousands of PropertyConditions would be created):

     

    private void Window_GotFocus(object sender, RoutedEventArgs e)

    {

        WindowInteropHelper helper = new WindowInteropHelper(this);

        AutomationElement mainWindowAutomationElement =    AutomationElement.FromHandle(helper.Handle);

        Automation.Automation.AddStructureChangedEventHandler(mainWindowAutomationElement, TreeScope.Descendants, AutomationFix);

         ((Window)sender).GotFocus -= Window_GotFocus;

    }

     

    private static readonly PropertyCondition AutomationFixCondition = new PropertyCondition(AutomationElement.NameProperty, "!!");

     

    void AutomationFix(object sender, StructureChangedEventArgs e)

    {

        AutomationElement element = sender as AutomationElement;

        element.FindFirst(TreeScope.Children, AutomationFixCondition);

    }

     

    Tuesday, October 05, 2010 12:29 PM
  • Thanks for the optimization. in addition you can optimize it further to:

    void AutomationFix(object sender, StructureChangedEventArgs e)

    {

        AutomationElement element = sender as AutomationElement;

        element.FindFirst(TreeScope.Element, AutomationFixCondition);

    }

    because the StructureChanged event occurs for all descendants so there is no need to search the element's children.

     

    In addition this issue appears to not happen on win 2003 and the fix seem to get the UI stuck instead (reversed affect)

    So I've added an "if" that will fix that: if (!(Environment.OSVersion.Version.Minor == 2 && Environment.OSVersion.Platform == PlatformID.Win32NT)) //if windows is not 2003

     

    If there are more machines that have the reversed affect please notify me on this forum.

    Thanks

    Ron Zeidman

    • Proposed as answer by MasTool Monday, October 18, 2010 7:33 AM
    Monday, October 11, 2010 3:58 PM
  • It was real hard to find out that it's InvalidateAutomationAncestors fault for slow expanders.

    I wish i had found this post earlier, I wasted 2 days on that :)

    Thank you very much for the workaround !

     

    Monday, October 18, 2010 7:36 AM
  • Pietervc,

    I know some time has passed, but we're experiencing this problem again after win7 sp1 and none of the workarounds found here works... Could you please tell me how you clear the AutomationEvents object? I don't even know where to find it... I agree that it is wrong, and I hate having to implement such code, but... our customers have BIG problems.

    Friday, March 25, 2011 8:13 AM
  • This bug is horrible. Fixes on this page works on most mashines. But found also case where they do hang the program for long long time and render it useless. SP1 is way way tolate!.
    Wednesday, April 13, 2011 10:05 PM
  • Thanks a ton for this Ron. If you don't mind my asking, how did you figure out what the issue was and how to work around it?

    I was really skeptical about this fix and after trying to get rid of everything that could be causing this issue, this is the only fix that worked and is allowing me to continue developing the app because apparently it only happens for me. I have a Win 7 SP1 x64 and Visual Studio 2010 SP 1 setup.

     

    Friday, April 22, 2011 12:42 AM
  • We found out that this fix Ron has failed on few computer. All XP and Vista. After some digging if you Install Automation 3.0 on them it works fine.
    Friday, April 22, 2011 1:21 AM
  • We're using the clearing AutomatinEvents reflection hack too, but doing it on a WM_WINDOWPOSCHANGING message.  It hasn't been noisy so far and it gets raised right after the AutomationEvent for the popup window had been added.
    Tuesday, May 03, 2011 3:06 PM
  • We encountered the same issue described here and used the AutomationFix workaround, thanks for that.

    - the problem is reproducing rather random and although it seems that it solves the problem, I'm not sure

    - I do not like calling some code that has nothing to do with the application itself

    Does anyone managed to find another fix for it?

     

    Thanks,

    Adrian

    Thursday, June 09, 2011 11:55 AM
  • We've encountered the same problem here which basically means we can't use large virtual WPF data grids or ListViews on Tablet PC's because of severe performance problems.

    In addition we even have a minor performance problem with the WPF TextBox which should be easy to reproduce in case someone is interested.

     

    Put a TextBox in a WPF window, start the app and copy a large amount of text into the TextBox (programmatically or manually, 100k chars should be sufficient).

    Focus the TextBox and type Ctrl-A (select all).

    If you have an automation client active like the Tablet PC Input Panel the GUI thread will hang with 100% on one core for several seconds. If not it is instant.

    Unfortunately the workaround posted by Ron doesn't seem to work in this case.

     

    Tuesday, June 14, 2011 8:37 AM
  • Hi Ron

    I have also performance issues with my WPF Application on some machines. I am asking me now, where I have to put your fix. Should I put it into all Windows, which my application shows in the Window_GotFocus Event?

    Thanks for your help.

     

    Best Regards, Thomas

    Wednesday, June 15, 2011 2:02 PM
  • Hi.

     

    I have on my side the same problem. I have now found, that the process asghost.exe is causing this. When I kill it, it works great.

    Is there a Timeframe, when Microsoft will fix this problem with Automation?

    Best Regards, Thomas

    Wednesday, July 06, 2011 11:32 AM
  • Hi,

    I'm experiencing the same problems. I have tried the first workaround by Ron Z (the 'new and improved Automation Fix' makes the problem even worse for me; but maybe I applied it incorrectly). This workaround works in certain cases.

    The biggest problem I experience with this fix is that systems that used to work correctly are now extremely slow (maybe even slower that the 'problem' systems).

    Is there a way to check if the workaround must be applied? 

    Some replies mention a workaround by clearing the AutomationEvents object via reflection in a background worker. Has someone an example of this approach?

     

    Thanks,

     

     

    Thursday, July 14, 2011 12:27 PM
  • Hi, this fixed worked for me for a cuple of days, but now on my machine I get:

    "Recursive call to Automation Peer API is not valid".

     

    WTF, Why??

    Sunday, July 17, 2011 5:08 AM
  • Hi,

    we experienced the same problem with our application and used the RemoveAutomationClient solution to fix it.

    By the way, I found this on the .NET support site: http://support.microsoft.com/kb/2484841/en-us , does anyone had the chance to test it?

    Tuesday, August 02, 2011 6:54 AM
  • We have tried this  http://support.microsoft.com/kb/2484841/en-us and it seems to solve our problems.

    The update package is available here http://archive.msdn.microsoft.com/KB2484841/Release/ProjectReleases.aspx?ReleaseId=5583

    • Proposed as answer by DonalSimmie Friday, August 19, 2011 8:55 AM
    Wednesday, August 03, 2011 4:15 PM
  • Hi Ron,

     

    Thanks for the Automation Fix. The fix is working for me. However, because of this problem, I found memory leak problem caused by the controls which are still strongly referenced by Automation Peer even when the control should be destroyed (for example in multi tab/window where the tab/window can be closed on user demand). Is there any suggestion to remedy this issue or any other workaround for the Automation problem?

     

    Thanks beforehand


    ~draconins
    Monday, August 08, 2011 4:14 AM
  • Thanks Viðar,

    This solution worked for me. That problem had been so frustrating it was severely affecting a vendor grid we are using.

    I wasn't able to check in the code behind fixes as they broke some other machines!

    Thanks again.

     

    Friday, August 19, 2011 8:55 AM
  • From DevExpress support center:

    private void RemoveAutomationClients()
    {
        Assembly pc = Assembly.GetAssembly(typeof(ContentElement));
        Type clmt = pc.GetType("System.Windows.ContextLayoutManager");
        MethodInfo mi = clmt.GetMethod("From", BindingFlags.Static | BindingFlags.NonPublic);
        object clm = mi.Invoke(null, new object[] { Dispatcher });
        PropertyInfo aepi = clm.GetType().GetProperty("AutomationEvents", BindingFlags.Instance | BindingFlags.NonPublic);
        object ae = aepi.GetValue(clm, null);
        FieldInfo cfi = ae.GetType().GetField("_count", BindingFlags.Instance | BindingFlags.NonPublic);
        FieldInfo hfi = ae.GetType().GetField("_head", BindingFlags.Instance | BindingFlags.NonPublic);
        MethodInfo rimi = ae.GetType().GetMethod("Remove", BindingFlags.Instance | BindingFlags.NonPublic);
        while (((int)cfi.GetValue(ae)) > 0)
        {
            object listItem = hfi.GetValue(ae);
            rimi.Invoke(ae, new object[] { listItem });
        }
    }
    
    


    http://www.devexpress.com/Support/Center/p/B181651.aspx?searchtext=B181651

    • Proposed as answer by MuhersenSni Sunday, November 13, 2011 12:44 PM
    Sunday, October 23, 2011 12:21 PM
  • Has anyone seen this issue in .net 3.5 sp1? I am facing a similar problem on a few machines.

    config: .net 3.5 SP1, wpf, citrix and infragistics

    Application runs fine until wpf messagebox pops up. But after that application is continuously slow till it is closed. Even typing in a textbox drags and CPU usage is upto 20%. I took a dump of the process and its stuck doing invalidateautomationancestors.

    Any ideas? I have not tried any of the above fixes yet.

    Best Regards,

    Vishal


    • Edited by Vishal_pai Tuesday, April 03, 2012 11:39 AM
    Tuesday, April 03, 2012 11:38 AM
  • in my case shutting down citrix system monitoring agent solved the drag. Now to next steps.
    Wednesday, April 04, 2012 12:22 PM
  • We have been having a very similar issue to this which we have found the devexpress work around resolved.  I found it a little alarming that still after a this long there is no official remedy which has been released.  This is our solution we adapted from the dev express one designed to be used in conjunction with the prism region manager. Hopefully it works for those who are in need of this. Please give me feed back about this solution as i am uncomfortable having it in production code. 

    /*
    Author            : James Harper
    Date Created      : 10/04/2012
    Description       : 
     * This is a fix to a bug in Microsoft .NET Framework 4.0-based WPF application (4.0.30319.439)
     * http://support.microsoft.com/kb/2484841/en-us#appliesto
     * Issue is poorly described by Microsoft as A .NET Framework 4.0-based WPF application may stop responding if you right-click a control to open a pop-up control
     * Further more the 4.0.30319.439 hot fix released did not resolve the issue. 
     * 
     * The issue is best describe by the UI thread being flooded with invalidate requests from controls resulting a long pause witnessed to be as long as 5 seconds at a time.
     * The issue is reproduced must more readably one tablet enabled systems and any other automation pairs. The issue has been noted to manifest when opening and closing
     * expander controls, popup items and maximising and minimising windows.
     * 
     * This issue is an adaptation of a fix documented by dev express for another manifestation of this issue
     * B181651 - DxLookupEdit - the drop-down window freezes when it used in in-place mode
     * http://www.devexpress.com/Support/Center/p/B181651.aspx?searchtext=B181651
     * 
     *   private void lookUpEdit_PopupOpening(object sender, RoutedEventArgs e)
     *  {
     *      RemoveAutomationClients();
     *  }
     *
     *  private void RemoveAutomationClients()
     *  {
     *      Assembly pc = Assembly.GetAssembly(typeof(ContentElement));
     *      Type clmt = pc.GetType("System.Windows.ContextLayoutManager");
     *      MethodInfo mi = clmt.GetMethod("From", BindingFlags.Static | BindingFlags.NonPublic);
     *      object clm = mi.Invoke(null, new object[] { Dispatcher });
     *      PropertyInfo aepi = clm.GetType().GetProperty("AutomationEvents", BindingFlags.Instance | BindingFlags.NonPublic);
     *      object ae = aepi.GetValue(clm, null);
     *      FieldInfo cfi = ae.GetType().GetField("_count", BindingFlags.Instance | BindingFlags.NonPublic);
     *      FieldInfo hfi = ae.GetType().GetField("_head", BindingFlags.Instance | BindingFlags.NonPublic);
     *      MethodInfo rimi = ae.GetType().GetMethod("Remove", BindingFlags.Instance | BindingFlags.NonPublic);
     *      while (((int)cfi.GetValue(ae)) > 0)
     *      { 
     *          object listItem = hfi.GetValue(ae);
     *          rimi.Invoke(ae, new object[] { listItem });
     *      }
     *  }
     *  
     *  Who ever wrote this made this unnecessary cryptic and a little slow because it uses reflections regularly.
     *  Below you will find more or less the same thing but only uses reflections once and is heavily commented
     *  My implementation of this fix is quite aggressive and attempts to remove all Automation events from a given framework item
     *  when it gains focus. This fix is designed to be used with prism the framework and applied to injected view.
     *  To This is cater for when Automation events are added once the app is already running. This would occur seldom
     *  but does break the fix on these occasions. 
     *  
     *  The essence of this fix uses reflection to gain access to private members of the context layout manage to obtain its Automation event list 
     *  and remove each item for it.
     *  
     * 
     * Other related resources:
     * 4.0.30319.439 hotfix - This seamed to have been hidden away by Microsoft?!
     * http://archive.msdn.microsoft.com/KB2484841/Release/ProjectReleases.aspx?ReleaseId=5583
     * 
     * MSDN thread discussing this issue
     * http://social.msdn.microsoft.com/Forums/en/windowsaccessibilityandautomation/thread/6c4465e2-207c-4277-a67f-e0f55eff0110
     * 
    */
    
    using System;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Threading;
    
    using Core.Services.Logging;
    
    namespace Core.Presentation.System
    {
        public class Kb2484841DotNetFix
        {
            // Used to monitor fix
            private readonly ILogger _logger;
    
            // Private member used for exposing private members via reflections 
            private readonly object _automationEvents;
            private readonly FieldInfo _countFieldInfo;
            private readonly FieldInfo _headfieldInfo;
            private readonly MethodInfo _removeMethodInfo;
    
    
            /// <summary>
            /// Initializes a new instance of the <see cref="Kb2484841DotNetFix"/> class.
            /// </summary>
            /// <param name="dispatcher">The dispatcher.</param>
            public Kb2484841DotNetFix(Dispatcher dispatcher, ILogger logger)
            {
                // We need this is see if our fix is playing up
                _logger = logger;
                
                #region 1: We need to find the context layout manager for the given ui thread
    
                // Find the Assmeby of where type ContentElement resides  
                Assembly assembly = Assembly.GetAssembly(typeof(ContentElement));
    
                // Get the Type of Context layout manager which is found is the assambly which contained ContentElement
                Type contextLayoutManagerType = assembly.GetType("System.Windows.ContextLayoutManager");
    
                // Use reflections to find the "static" and "Non Pubic" Method named "from" 
                MethodInfo fromMethodInfo = contextLayoutManagerType.GetMethod("From", BindingFlags.Static | BindingFlags.NonPublic);
    
                // We get the contextLayoutManager from the static "From" method via the current dispacter
                object contextLayoutManager = fromMethodInfo.Invoke(null, new object[] { dispatcher });
    
                #endregion
    
                #region 2: We need to get AutomationEvents for the layout events
    
                // Get the PropertyInfo of the "Non Pubic" property named "AutomationEvents"
                PropertyInfo automationEventspropertyInfo = contextLayoutManager.GetType().GetProperty("AutomationEvents", BindingFlags.Instance | BindingFlags.NonPublic);
    
                // Get the value of automationEventspropertyInfo
                _automationEvents = automationEventspropertyInfo.GetValue(contextLayoutManager, null);
    
                #endregion
    
                #region 3: We need get the tools to enumerate the AutomationEvents and remove them
    
                // We need to get tools required to enumerate the AutomationEvents list
    
                // We get the private memeber "_count" using reflections
                _countFieldInfo = _automationEvents.GetType().GetField("_count", BindingFlags.Instance | BindingFlags.NonPublic);
    
                // We get the private memeber "_head" using reflections
                _headfieldInfo = _automationEvents.GetType().GetField("_head", BindingFlags.Instance | BindingFlags.NonPublic);
    
                // We get the private method "Remove" using reflections
                _removeMethodInfo = _automationEvents.GetType().GetMethod("Remove", BindingFlags.Instance | BindingFlags.NonPublic);
    
                #endregion
            }
    
            #region Reflection wrappers
    
            /// <summary>
            /// Gets the count of the layout list.
            /// </summary>
            private int LayoutListCount
            {
                get { return (int)_countFieldInfo.GetValue(_automationEvents); }
            }
    
            /// <summary>
            /// Gets the head of the automation event items.
            /// </summary>
            private object AutomationEventsHeadItem
            {
                get { return _headfieldInfo.GetValue(_automationEvents); }
            }
    
            /// <summary>
            /// Removes a given event item.
            /// </summary>
            /// <param name="item">The item.</param>
            public void RemoveAutomationEventItem(object item)
            {
                 _removeMethodInfo.Invoke(_automationEvents, new[] { item });
            }
    
            #endregion
    
            #region Apply Fix
            
            /// <summary>
            /// Removes the all automation events from the context Layout Manager on the dispatcher used to initialize this instance
            /// </summary>
            public void RemoveAutomationEvents()
            {
      
                var count = LayoutListCount;
                Exception error = null;
    
                if (count == 0)
                    return;
              
                _logger.Debug("KB2484841 .Net Fix Detected {0} Automation Events, attempting to remove events", count);
    
                while (count > 0)
                {
                    try
                    {
                        RemoveAutomationEventItem(AutomationEventsHeadItem);
                    }
                    catch (Exception e)
                    {
                        // We want to prevent the noisy logging, logging up a storm. Log as debug and remember the last error and log that as error
                        _logger.Debug(e, "KB2484841 .Net Fix Detected Automation Event which was unsuccessfully removed, attempting to continue");
                        error = e;
                    }
    
                    // We make the assumption that this was successfully removed 
                    // to prevent us getting stuck in a loop
                    count--;
                }
    
                if (error != null)
                {
                    _logger.Error(error, "KB2484841 .Net Fix Detected one or more Automation Events which were unsuccessfully removed");
                }
            }
    
            public void ApplyFix(FrameworkElement frameworkElement)
            {
                // The fix is to remove any attached events to the item when it receives focus.
                // This mightn't be the best way to do this, be carful making changes at this issue can be hard to reproduce
                // Issue seems most prevalent on tablet enabled systems
                frameworkElement.GotFocus += FrameworkElementGotFocus;
            }
    
            /// <summary>
            /// Framework the element got focus.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
            void FrameworkElementGotFocus(object sender, RoutedEventArgs e)
            {
                // Remove any attached Automation Events
                RemoveAutomationEvents();
            }
    
            #endregion
        }
    }
    

    Tuesday, April 10, 2012 2:02 AM
  • Hi James

    Can you give me a sample how to use your class Kb2484841DotNetFix?

    Thank you.

    Thomas

    Friday, April 13, 2012 5:32 PM
  • I just implemented Hemiharper's method in my WPF application.

    Things to know:
    1. It is not a 1-time use, you need to call it periodically or after events that trigger the UIAutomation bug arise.
    2. You can not call it outside of your app - it's not a system-wide cleanup.  That should be obvious for anyone used to using the Dispatcher, but not to me.
    3. The Dispatcher.CurrentDispatcher is thread dependent.  Like #2, this is probably obvious to others, but not to me.  When I create my BackgroundWorker, I pass in the offending thread's CurrentDispatcher.
    4. I implemented it with a BackgroundWorker (still testing), that calls the fix every second.  I'm sure this is incredibly wasteful, but I can live with it for now.  I would love to call it after some generic set of events are called, but since I don't know exactly WHAT is causing it, this is my only fix.

    My implementation:
    public myWPFConstructor()
    {
       InitializeComponent();
       // This passes the CurrentDispatcher to the BackgroundWorker we're about to create and call.
       DotNetFix(Dispatcher.CurrentDispatcher);
    }

            public void DotNetFix(Dispatcher _dispatcher)
            {
                dotNetFixWorker.DoWork += delegate(object s, DoWorkEventArgs args)
                {
                    while (true)
                    {
                        Kb2484841DotNetFix DotNetFix = new Kb2484841DotNetFix(_dispatcher);
                        DotNetFix.RemoveAutomationEvents();
                        Thread.Sleep(1000);
                    }
                };
                dotNetFixWorker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
                {
                    MessageBox.Show("DotNetFix Completed!");
                };
                dotNetFixWorker.RunWorkerAsync();
            }

    ------------------------------------------
    For completeness, here is my modified class - I have modified some things (logging) that aren't necessary in my implementation.
    ------------------------------------------
    /*
    Author            : James Harper
    Date Created      : 10/04/2012
    Description       :
     * This is a fix to a bug in Microsoft .NET Framework 4.0-based WPF application (4.0.30319.439)
     * http://support.microsoft.com/kb/2484841/en-us#appliesto
     * Issue is poorly described by Microsoft as A .NET Framework 4.0-based WPF application may stop responding if you right-click a control to open a pop-up control
     * Further more the 4.0.30319.439 hot fix released did not resolve the issue.
     *
     * The issue is best describe by the UI thread being flooded with invalidate requests from controls resulting a long pause witnessed to be as long as 5 seconds at a time.
     * The issue is reproduced must more readably one tablet enabled systems and any other automation pairs. The issue has been noted to manifest when opening and closing
     * expander controls, popup items and maximising and minimising windows.
     *
     * This issue is an adaptation of a fix documented by dev express for another manifestation of this issue
     * B181651 - DxLookupEdit - the drop-down window freezes when it used in in-place mode
     * http://www.devexpress.com/Support/Center/p/B181651.aspx?searchtext=B181651
     *
     *   private void lookUpEdit_PopupOpening(object sender, RoutedEventArgs e)
     *  {
     *      RemoveAutomationClients();
     *  }
     *
     *  private void RemoveAutomationClients()
     *  {
     *      Assembly pc = Assembly.GetAssembly(typeof(ContentElement));
     *      Type clmt = pc.GetType("System.Windows.ContextLayoutManager");
     *      MethodInfo mi = clmt.GetMethod("From", BindingFlags.Static | BindingFlags.NonPublic);
     *      object clm = mi.Invoke(null, new object[] { Dispatcher });
     *      PropertyInfo aepi = clm.GetType().GetProperty("AutomationEvents", BindingFlags.Instance | BindingFlags.NonPublic);
     *      object ae = aepi.GetValue(clm, null);
     *      FieldInfo cfi = ae.GetType().GetField("_count", BindingFlags.Instance | BindingFlags.NonPublic);
     *      FieldInfo hfi = ae.GetType().GetField("_head", BindingFlags.Instance | BindingFlags.NonPublic);
     *      MethodInfo rimi = ae.GetType().GetMethod("Remove", BindingFlags.Instance | BindingFlags.NonPublic);
     *      while (((int)cfi.GetValue(ae)) > 0)
     *      {
     *          object listItem = hfi.GetValue(ae);
     *          rimi.Invoke(ae, new object[] { listItem });
     *      }
     *  }
     *  
     *  Who ever wrote this made this unnecessary cryptic and a little slow because it uses reflections regularly.
     *  Below you will find more or less the same thing but only uses reflections once and is heavily commented
     *  My implementation of this fix is quite aggressive and attempts to remove all Automation events from a given framework item
     *  when it gains focus. This fix is designed to be used with prism the framework and applied to injected view.
     *  To This is cater for when Automation events are added once the app is already running. This would occur seldom
     *  but does break the fix on these occasions.
     *  
     *  The essence of this fix uses reflection to gain access to private members of the context layout manage to obtain its Automation event list
     *  and remove each item for it.
     *  
     *
     * Other related resources:
     * 4.0.30319.439 hotfix - This seamed to have been hidden away by Microsoft?!
     * http://archive.msdn.microsoft.com/KB2484841/Release/ProjectReleases.aspx?ReleaseId=5583
     *
     * MSDN thread discussing this issue
     * http://social.msdn.microsoft.com/Forums/en/windowsaccessibilityandautomation/thread/6c4465e2-207c-4277-a67f-e0f55eff0110
     *
    */

    using System;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Threading;

    //using Core.Services.Logging;
    using Oasis.Logging;
    using Oasis.Foundation.Infrastructure;


    namespace Oasis.Presentation
    {
        public class Kb2484841DotNetFix
        {
            // Used to monitor fix
            //private readonly IOasisLogger _logger;

            // Private member used for exposing private members via reflections
            private readonly object _automationEvents;
            private readonly FieldInfo _countFieldInfo;
            private readonly FieldInfo _headfieldInfo;
            private readonly MethodInfo _removeMethodInfo;


            /// <summary>
            /// Initializes a new instance of the <see cref="Kb2484841DotNetFix"/> class.
            /// </summary>
            /// <param name="dispatcher">The dispatcher.</param>
            public Kb2484841DotNetFix(Dispatcher dispatcher)//, IOasisLogger logger)
            {
                // We need this is see if our fix is playing up
                //_logger = logger;

                #region 1: We need to find the context layout manager for the given ui thread

                // Find the Assmeby of where type ContentElement resides  
                Assembly assembly = Assembly.GetAssembly(typeof(ContentElement));

                // Get the Type of Context layout manager which is found is the assambly which contained ContentElement
                Type contextLayoutManagerType = assembly.GetType("System.Windows.ContextLayoutManager");

                // Use reflections to find the "static" and "Non Pubic" Method named "from"
                MethodInfo fromMethodInfo = contextLayoutManagerType.GetMethod("From", BindingFlags.Static | BindingFlags.NonPublic);

                // We get the contextLayoutManager from the static "From" method via the current dispacter
                object contextLayoutManager = fromMethodInfo.Invoke(null, new object[] { dispatcher });

                #endregion

                #region 2: We need to get AutomationEvents for the layout events

                // Get the PropertyInfo of the "Non Pubic" property named "AutomationEvents"
                PropertyInfo automationEventspropertyInfo = contextLayoutManager.GetType().GetProperty("AutomationEvents", BindingFlags.Instance | BindingFlags.NonPublic);

                // Get the value of automationEventspropertyInfo
                _automationEvents = automationEventspropertyInfo.GetValue(contextLayoutManager, null);

                #endregion

                #region 3: We need get the tools to enumerate the AutomationEvents and remove them

                // We need to get tools required to enumerate the AutomationEvents list

                // We get the private memeber "_count" using reflections
                _countFieldInfo = _automationEvents.GetType().GetField("_count", BindingFlags.Instance | BindingFlags.NonPublic);

                // We get the private memeber "_head" using reflections
                _headfieldInfo = _automationEvents.GetType().GetField("_head", BindingFlags.Instance | BindingFlags.NonPublic);

                // We get the private method "Remove" using reflections
                _removeMethodInfo = _automationEvents.GetType().GetMethod("Remove", BindingFlags.Instance | BindingFlags.NonPublic);

                #endregion
            }

            #region Reflection wrappers

            /// <summary>
            /// Gets the count of the layout list.
            /// </summary>
            private int LayoutListCount
            {
                get { return (int)_countFieldInfo.GetValue(_automationEvents); }
            }

            /// <summary>
            /// Gets the head of the automation event items.
            /// </summary>
            private object AutomationEventsHeadItem
            {
                get { return _headfieldInfo.GetValue(_automationEvents); }
            }

            /// <summary>
            /// Removes a given event item.
            /// </summary>
            /// <param name="item">The item.</param>
            public void RemoveAutomationEventItem(object item)
            {
                _removeMethodInfo.Invoke(_automationEvents, new[] { item });
            }

            #endregion

            #region Apply Fix

            /// <summary>
            /// Removes the all automation events from the context Layout Manager on the dispatcher used to initialize this instance
            /// </summary>
            public void RemoveAutomationEvents()
            {

                var count = LayoutListCount;
                Exception error = null;

                if (count == 0)
                    return;

                //_logger.WriteEntry("KB2484841 .Net Fix Detected {0} Automation Events, attempting to remove events", count);

                while (count > 0)
                {
                    try
                    {
                        RemoveAutomationEventItem(AutomationEventsHeadItem);
                    }
                    catch (Exception e)
                    {
                        // We want to prevent the noisy logging, logging up a storm. Log as debug and remember the last error and log that as error
                        //_logger.WriteEntry("KB2484841 .Net Fix Detected Automation Event which was unsuccessfully removed, attempting to continue");
                        error = e;
                    }

                    // We make the assumption that this was successfully removed
                    // to prevent us getting stuck in a loop
                    count--;
                }

                if (error != null)
                {
                    //_logger.WriteEntry("KB2484841 .Net Fix Detected one or more Automation Events which were unsuccessfully removed: " + error.ToString());
                }
            }

            public void ApplyFix(FrameworkElement frameworkElement)
            {
                // The fix is to remove any attached events to the item when it receives focus.
                // This mightn't be the best way to do this, be carful making changes at this issue can be hard to reproduce
                // Issue seems most prevalent on tablet enabled systems
                frameworkElement.GotFocus += FrameworkElementGotFocus;
            }

            /// <summary>
            /// Framework the element got focus.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
            void FrameworkElementGotFocus(object sender, RoutedEventArgs e)
            {
                // Remove any attached Automation Events
                RemoveAutomationEvents();
            }

            #endregion
        }
     }
    Wednesday, May 02, 2012 6:09 PM
  • This problem affects me quite badly and I am quite surprised there is no official fix within the .NET framework as yet. I have tried the hotfix but it made no difference to my setup. I will investigate the above suggested solutions however from a performance point of view they are very excessive and I would rather avoid.

    Is there an estimated time frame to have this fixed properly? (.NET 4.0 Sp1?)

    Thanks,

    Mike


    • Edited by MikeRES78 Wednesday, July 11, 2012 1:20 PM
    Wednesday, July 11, 2012 1:12 PM
  • Thanks for the optimization. in addition you can optimize it further to:

    void AutomationFix(object sender, StructureChangedEventArgs e)

    {

        AutomationElement element = sender as AutomationElement;

        element.FindFirst(TreeScope.Element, AutomationFixCondition);

    }

    because the StructureChanged event occurs for all descendants so there is no need to search the element's children.

     

    In addition this issue appears to not happen on win 2003 and the fix seem to get the UI stuck instead (reversed affect)

    So I've added an "if" that will fix that: if (!(Environment.OSVersion.Version.Minor == 2 && Environment.OSVersion.Platform == PlatformID.Win32NT)) //if windows is not 2003

     

    If there are more machines that have the reversed affect please notify me on this forum.

    Thanks

    Ron Zeidman

    So I know this is a total necro-post, but this solution has fixed my issue. However, it appears as though it has killed the ability to use automation testing like Ranorex or Project white. Do you have any experience with this? Possible solutions?

    Thanks.

    Wednesday, July 31, 2013 1:16 PM