首页 > 解决方案 > How to force UI automation tree refresh

问题描述

I am currently trying to use System.Windows.Automation to automate a chrome instance, but at a certain point AutomationElement.FindAll(TreeScope.Children, Condition.TrueCondition); returns always 0 children, but I can see them in inspect.exe. When I hit refresh in inspect.exe the elements show up in my app too.

While looking for a solution, I found this SO post, that OP had more or less the same issue. An answer suggested to use:

SystemParametersInfo( SPI_SETSCREENREADER, TRUE, NULL, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
PostMessage( HWND_BROADCAST, WM_WININICHANGE, SPI_SETSCREENREADER, 0);

I implemented that as:

[DllImport("user32.dll", SetLastError = true)]
static extern bool SystemParametersInfo(int uiAction, int uiParam, IntPtr pvParam, int fWinIni);

[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);

....

const int SPI_SETSCREENREADER = 0x0047;

IntPtr inptrnull = IntPtr.Zero;
const int SPIF_UPDATEINIFILE = 0x01;
const int SPIF_SENDCHANGE = 0x02;


IntPtr HWND_BROADCAST = new IntPtr(0xffff);
const int WM_WININICHANGE = 0x001A;

SystemParametersInfo(SPI_SETSCREENREADER, 1, inptrnull, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
PostMessage(HWND_BROADCAST, WM_WININICHANGE, SPI_SETSCREENREADER, 0);

But it has no effect.

What am I missing? Is there a other/better way to this?

标签: c#user-interfaceui-automationinspect.exe

解决方案


Looks like you're using an obsolete managed UIA implementation (System.Windows.Automation). It's a known case when it could return not all of the children, it might be the cause.

As per the documentation:

For Windows 7, the API has been rewritten in the Component Object Model (COM). Although the library functions introduced in the earlier version of UI Automation are still documented, they should not be used in new applications.

You might try to use UIA COM interface instead (Inspect uses the same approach):

  1. Add a reference to COM -> UIAutomationClient to your project. This will generate a wrapper for you, so the COM interface will be accessible in the code in a usual OO-style.

  2. Create a UI Automation object and do your stuff with UIA elements:

    IUIAutomation automation = new CUIAutomation();
    // Start with the root element; you could also get an element from the point or current focus
    IUIAutomationElement element = automation.GetRootElement();
    IUIAutomationElementArray foundElements = element.FindAll(TreeScope.TreeScope_Children, automation.CreateTrueCondition());
    for (int i = 0; i < foundElements.Length; i++)
    {
        IUIAutomationElement currentElement = foundElements.GetElement(i);
        // do your stuff with the element: get its properties, etc
    }
    
  3. If the above doesn't help, you might try to use RawTreeWalker instead of calling FindAll() to traverse the UI tree:

    IUIAutomationTreeWalker walker = automation.RawViewWalker;
    IUIAutomationElement child = walker.GetFirstChildElement(element);
    var sibling = walker.GetNextSiblingElement(child);
    while (sibling != null)
    {
        // do your stuff with sibling elements (get their child elements, etc)
        sibling = walker.GetNextSiblingElement(child);
    }
    
  4. I've automated Chrome prevously using UIA and as far as I remember it might respond to UIA calls a bit odd: e.g. sometimes it returns a container element instead of the actual one (button, edit, etc). I resolved this case by doing several attempts until I finally retrieved what expected. What if you call FindAll() several times too?


推荐阅读