dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.1k stars 1.17k forks source link

Windows Automation tree breaking change #8679

Open Tobiidave opened 10 months ago

Tobiidave commented 10 months ago

Description

Dotnet 8 renders the controls in the Windows automation tree differently compared to earlier dotnet versions. Buttons in an itemscontrol can't be found anymore, breaking the existing automation.

Using the "Accessabilitiy Insights for Windows" shows a different graph compared to dotnet 6/7 (see below)

Reproduction Steps

WpfApp3.zip

Expected behavior

With <TargetFramework>net7.0-windows</TargetFramework> , the automation tree looks like pane "Desktop 1" window "MainWindow" title bar '' button "TopButton" button "ItemButton1"

Actual behavior

With <TargetFramework>net8.0-windows</TargetFramework>, the automation tree looks like

pane "Desktop 1" window "MainWindow" title bar '' button "TopButton" list view #note that here was the "ItemButton1" - now it is not present anymore!

Regression?

Yes

Known Workarounds

No response

Impact

The UI Tests for our applications breaks; need workarounds

Configuration

Microsoft.WindowsDesktop.App 8.0.1 Microsoft.NETCore.App 8.0.1 windows 10.19045.3803 x64

Vs2022

Other information

No response

lindexi commented 10 months ago

@Tobiidave Maybe this behavior be changed by https://github.com/dotnet/wpf/pull/6862

I'm not sure.

Tobiidave commented 10 months ago

I don't know how the 6862 affected this. But the problem exists in both 8.0.0 and 8.0.1

psmulovics commented 10 months ago

I do not think you can expect automation trees to stay intact between major version upgrades - I would consider it business-as-usual these getting broken on major upgrade.

Tobiidave commented 10 months ago

@psmulovics In this case the WPF controls change type and are not usable anymore, I edited and tried to emphasize this by a small comment. It is not only a tree re-ordering issue, but elements that were clickable can not be found anymore! Ofc, any suggested workarounds are welcome! We have paused dotnet8 migration due to this, the automation tech is central to our development.

lindexi commented 10 months ago

We have paused dotnet8 migration due to this

@Tobiidave Sorry hear that. I still haven't found the exact change code for this issues.

jimm98y commented 10 months ago

I've created a test app and I've tried to restore the original behavior simply by setting this in OnStartup:

System.AppContext.SetSwitch("Switch.System.Windows.Controls.ItemsControlDoesNotSupportAutomation", true);

Unfortunately, it looks like it's too late and the value is already set and cached. So I had to use reflection instead:

var accessibilitySwitches = Type.GetType("System.Windows.AccessibilitySwitches, WindowsBase, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
var field = accessibilitySwitches.GetField("_ItemsControlDoesNotSupportAutomation", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | BindingFlags.DeclaredOnly);
field.SetValue(null, 1);

This seems to fix the issue and restore the original pre-NET8 behavior. To test it, I used the following piece of code in the test app from this bug report:

public static void VerifyMainWindowAccessibility()
{
    AutomationElement mainWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "MainWindow"));
    Debug.WriteLine(DumpUIATree(mainWindow));
}

private static string DumpUIATree(AutomationElement element)
{
    var s = element.Current.Name + " : " + element.Current.ControlType.ProgrammaticName;
    DumpChildrenRecursively(element, 1, ref s);
    return s;
}

private static List<AutomationElement> GetChildNodes(AutomationElement automationElement)
{
    var children = new List<AutomationElement>();
    TreeWalker walker = TreeWalker.ControlViewWalker;
    AutomationElement child = walker.GetFirstChild(automationElement);
    while (child != null)
    {
        children.Add(child);
        child = walker.GetNextSibling(child);
    }
    return children;
}

private static void DumpChildrenRecursively(AutomationElement node, int level, ref string s)
{
    var children = GetChildNodes(node);
    foreach (AutomationElement child in children)
    {
        if (child != null)
        {
            for (int i = 0; i < level; i++)
                s += "-";
            s += " " + child.Current.Name + " : " + child.Current.ControlType?.ProgrammaticName + "\r\n";
            DumpChildrenRecursively(child, level + 1, ref s);
        }
    }
}

NET8 before the fix:

- TopButton : ControlType.Button
-- TopButton : ControlType.Text
-  : ControlType.List
-- ItemButton1 : ControlType.DataItem
--- ItemButton1 : ControlType.Text

NET8 after the fix:

- TopButton : ControlType.Button
-- TopButton : ControlType.Text
- ItemButton1 : ControlType.Button
-- ItemButton1 : ControlType.Text

NET6 behavior:

- TopButton : ControlType.Button
-- TopButton : ControlType.Text
- ItemButton1 : ControlType.Button
-- ItemButton1 : ControlType.Text
Kuldeep-MS commented 10 months ago

I am investigating the issue from my end.

Tobiidave commented 10 months ago

Good work guys - but be sure to test this with the ms app "Accessibilities for Windows" to make sure it looks the same. It shows larger differences than what I see from your inspection using the TreeWalker-code above!

walterlv commented 10 months ago

I've tested this code below to set the AppContext switch and find it worked.

public class Program
{
    [STAThread]
    static void Main()
    {
        AppContext.SetSwitch("Switch.System.Windows.Controls.ItemsControlDoesNotSupportAutomation", true);

        var app = new App();
        app.InitializeComponent();
        app.Run();
    }
}

This means that if it is late on startup, it can be set before the WPF initialization which reflection is not needed. @jimm98y

Tobiidave commented 3 months ago

Hi. So now when .Net7 is out of support and this issue is blocking from moving to .Net8, what do think we should do with our app and +1000 customers?

lindexi commented 3 months ago

@Tobiidave Sorry, I do not think the PR can enter .NET 9

Tobiidave commented 3 months ago

So how would we handle these changes in the windows automation tree?

Kuldeep-MS commented 3 months ago

@Tobiidave - Could you please take a look at this comment, where the same scenario is explained in detail? Please let us know if it isn't working for you.

Tobiidave commented 3 months ago

Hi - I took the test project attached to this thread, added

    public App()
    {
        AppContext.SetSwitch("Switch.System.Windows.Controls.ItemsControlDoesNotSupportAutomation", true);

        // Create the startup window
        MainWindow mainWindow = new MainWindow();
        mainWindow.Show();
    }

But the automation tree did not change.