Skip to content

TextBox.AppendText much slower for large amounts of text in TextBox after showing WPF menu due to AutomationPeer creation #8813

@rwg0

Description

@rwg0

Description

Adding large quantities of text to a TextBox using TextBox.AppendText repeatedly becomes significantly slower if the application hosting the textbox has ever had a WPF submenu opened.

Opening a submenu inside the application seems to trigger the AutomationPeer system to be active from that point on. This changes the performance behaviour of TextBox.AppendText, seemingly to the point where the time taken is proportional to the amount of text already in the text box. This leads to classic O(N^2) performance if you repeatedly add content to a text box.

The cause can be confirmed by overriding OnCreateAutomationPeer() on the containing Window to return a dummy AutomationPeer, which restores performance

Reproduction Steps

Set up the following code in a WPF application (.NET 8 shows the problem, as does 7)

<Window x:Class="WPFTextPerf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="Measure speed before and after showing this menu">
                <MenuItem Header="Open"></MenuItem>
            </MenuItem>
        </Menu>
        <Button Grid.Row="1" Click="Button_Click">Click Me to measure speed of TextBox population!</Button>
        <TextBox AutomationProperties.IsOffscreenBehavior="Offscreen" Grid.Row="2" Name="TextBox" IsReadOnly="True" FontFamily="Courier New" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible"></TextBox>

    </Grid>
</Window>

namespace WPFTextPerf2;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        TextBox.Clear();
        var sw = Stopwatch.StartNew();
        int count = 3000;
        for (int i=0; i<count; i++)
        {
            TextBox.AppendText($"This is line {i} of {count}\r\n");
            await Task.Yield();
        }
        MessageBox.Show($"Loaded {count} lines in {sw.ElapsedMilliseconds}ms");
    }

    //protected override AutomationPeer OnCreateAutomationPeer()
    //{
    //    return new CustomWindowAutomationPeer(this);
    //}
}

public class CustomWindowAutomationPeer : FrameworkElementAutomationPeer
{
    public CustomWindowAutomationPeer(FrameworkElement owner) : base(owner) { }

    protected override string GetNameCore() => "NoAutomationPeer";

    protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Window;

    protected override List<AutomationPeer> GetChildrenCore() => new();
}

Run the application and press the 'Click Me' button to time how long it takes to add 3000 lines of text to the text box. Click repeatedly to remove any 'cold start' effects. In my testing, after 2-3 clicks, the time taken ends up at about 50-60ms

Now click on the menu in the application to show the sub menu. No need to activate the sub-menu item, just expanding the menu is sufficient.

Return to the 'Click Me' button and re-check the timing. The timing to populate the text box increases significantly (in my measurements about 2.5s, or 50x slower than before showing the menu).

Expected behavior

Performance of other parts of the application should not be significantly affected by unrelated actions (opening and closing a menu)

Actual behavior

Performance of populating the text box slows by a factor of 50 after the menu has been opened.

The size of the effect grows as the amount of text to be added increases, for instance for 5000 lines instead of 3000, the timings go from 100ms to 7.5s - 75x slower.

Regression?

The issue reproduces in 6,7 and 8. Not tested earlier versions than that.

Known Workarounds

override OnCreateAutomationPeer on the Window containing the text box (or, presumably, at some other point in the WPF hierarchy leading to the text box if it is in a more complex Window). Return a custom AutomationPeer that does not list child objects, cutting the TextBox off from automation

    protected override AutomationPeer OnCreateAutomationPeer()
    {
        return new CustomWindowAutomationPeer(this);
    }

    public class CustomWindowAutomationPeer : FrameworkElementAutomationPeer
    {
        public CustomWindowAutomationPeer(FrameworkElement owner) : base(owner) { }

        protected override string GetNameCore() => "NoAutomationPeer";

        protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Window;

        protected override List<AutomationPeer> GetChildrenCore() => new();
    }

Impact

While the use of large amounts of text in a TextBox may be quite rare, the very significant nature of the slowdown and the fact that it is triggered by a seemingly unrelated action offset that to add to the impace. Tracking the cause back to automation peers wasn't simple, meaning that many developers may not discover the workaround. In any case, the workaround isn't really a good one, since it cuts the entire Window off from automation/accessibility.

If a performance fix for this issue is impossible, a way to cleanly cut off just particularly XAML elements from automation to avoid this sort of perf problem would be good.

Configuration

.NET 8.0.1
Windows 11 Pro 23H2
x64 system, tested with the application targeted at both x64 and x86. The exact timings vary slightly between x64 and x86, but the problem persists.
Appears not to be configuration specific

Other information

No response

Metadata

Metadata

Assignees

Labels

InvestigateRequires further investigation by the WPF team.PerformancePerformance related issueTextBox

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions