Opening a Popup or ToolTip causes AutomationPeers to be created
- .NET Core Version: Not relevant as it happens in all .NET versions
- Windows version: Windows 11 (but happens on all OS versions)
- Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
Problem description:
After the first Popup (or ToolTip) is opened WPF starts creating AutomationPeers and starts firing automation events, even if no automation client is listening.
This massively degrades performance, especially in DataGrid or when a lot of controls are present/created.
This is caused by ForceMsaaToUiaBridge in Popup https://github.com/dotnet/wpf/blob/1bc136f003f0879686f53a47f984c7809b183014/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/Popup.cs#L3451
In addition to this:
What's also quite expensive is that the default value for AutomationProperties.ItemStatusProperty is string.Empty and the default value for _itemStatus in AutomationPeer is null.
This causes change notifications being sent for AutomationElementIdentifiers.ItemStatusProperty for every AutomationPeer that gets created. Why also consumes a lot of CPU cycles. Applying a hack which changes the default value for AutomationProperties.ItemStatusProperty to null cuts down the time spent raising pointless change notifications in half.
Actual behavior:
AutomationPeers get created after first Popup is opened.
Expected behavior:
AutomationPeers are only created when an automation client is present.
Minimal repro:
- Create a new WPF application
- Add a control with a tooltip
- Hover over control to make tooltip visible
AutomationPeers get created
This might also be the reason automation peers cause memoryleaks, i think i've came across this atleast once too.
hi just found this thing ... anyone found something ?
For anyone trying to change the default value for AutomationProperties.ItemStatusProperty you can use:
private static readonly FieldInfo? propertyMetadataDefaultValueFieldInfo = typeof(PropertyMetadata).GetField("_defaultValue", BindingFlags.Instance | BindingFlags.NonPublic);
private static void ForceOverwriteDefaultValue(DependencyProperty property, object? newValue)
{
if (property.DefaultMetadata.DefaultValue == newValue)
{
return;
}
propertyMetadataDefaultValueFieldInfo?.SetValue(property.DefaultMetadata, newValue);
}
and call it with ForceOverwriteDefaultValue(AutomationProperties.ItemStatusProperty, null);.
Use this at your own risk as modifying things through reflection might be dangerous. You should also use this hack as early in your application lifecycle as possible
Any feedback on this?
If this is (and I think it is) related to what I am experiencing, sometimes it causes BIG performance degradations.
A simple DataGrid with 10 rows and a DataGridTemplateColumn with a CellTemplate containing a TextBlock with ToolTip causes WPF to go insane with AutomationPeer calls, without automation clients.
If it would be of help, I can try to create a simple repro repo of that scenario.
The hack provided by @batzen helped, but the problem persists.
This is a hot path of my application in .NET 7, without any automation client:

I tried @batzen reflection solution but it did not help with my datagrid when a combobox is opened.
@LoRdPMN
It appears I was able to solve my data grid sluggish issue by making my own DataGrid class and returning null in OnCreateAutomationPeer. I'm not sure what ramifications this may have but it appears to work,.
public class MyDataGrid : DataGrid
{
public MyDataGrid()
{
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return null;
//return base.OnCreateAutomationPeer();
}
}
hello i have it will send U morning :)
hello i have it will send U morning :)
Thank you! I would love to see your solution.
So i have used Harmony ( PackageReference Include="HarmonyX" Version="2.10.2" ) it makes override for part of the framework code and then:
[ObfuscationAttribute(Exclude = true)]
[HarmonyPatch(typeof(UIElementAutomationPeer), "CreatePeerForElement")]
class PopupFix
{
static bool Prefix(UIElement element)
{
return false;
}
}
I tried @batzen reflection solution but it did not help with my datagrid when a combobox is opened.
@LoRdPMN
It appears I was able to solve my data grid sluggish issue by making my own DataGrid class and returning null in OnCreateAutomationPeer. I'm not sure what ramifications this may have but it appears to work,.
public class MyDataGrid : DataGrid { public MyDataGrid() { } protected override AutomationPeer OnCreateAutomationPeer() { return null; //return base.OnCreateAutomationPeer(); } }
After more testing it doesn't seem to help datagrids with any sort of complexity to them. It's still an issue for me.
I found a solution that works for my problem. I had to override the Windows class. From there you override the OnCreateAutomationPeer() method.
public class CustomWindowAutomationPeer : FrameworkElementAutomationPeer
{
public CustomWindowAutomationPeer(FrameworkElement owner) : base(owner) { }
protected override string GetNameCore()
{
return "CustomWindowAutomationPeer";
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Window;
}
protected override List<AutomationPeer> GetChildrenCore()
{
return new List<AutomationPeer>();
}
}
In your custom Window class add
protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
{
return new CustomWindowAutomationPeer(this);
}