Skip to content

UiaProvider implementation forces virtual ListView to retrieve ALL ListViewItems at WM_DESTROY #13922

@gerhard17

Description

@gerhard17

.NET version

9.0

Did it work in .NET Framework?

Not tested/verified

Did it work in any of the earlier releases of .NET Core or .NET 5+?

I saw in the source code a dependency to OsVersion.IsWindows8OrGreater()

Issue description

I am using a ListView with VirtualMode = true to display a list a few thousands items.
When the ListView is destroyed, the OnRetrieveVirtualItem() is called for each index (even never retrieved ones).
This kills somehow the performance of the control.

Analyses as far I saw it in the debugger:

ListView overrides the internal bool SupportsUiaProviders => true;
First problem: I cannot change that because it's internal.

WM_DESTROY invokes on the Control class

private void WmDestroy(ref Message m)
{
	if (!RecreatingHandle && !Disposing && !IsDisposed && GetState(States.TrackingMouseEvent))
	{
		OnMouseLeave(EventArgs.Empty);
		UnhookMouseEvent();
	}
	if (SupportsUiaProviders)  // <--- THIS RETURNS TRUE
	{
		ReleaseUiaProvider(HWNDInternal);  // <--- THIS GETS CALLED
	}
	OnHandleDestroyed(EventArgs.Empty);
	if (!Disposing)
	{
		if (!RecreatingHandle)
		{
			SetState(States.Created, value: false);
		}
	}
	else
	{
		SetState(States.Visible, value: false);
	}
	DefWndProc(ref m);
}

This invokes on the ListView class

internal override bool SupportsUiaProviders => true;

internal override void ReleaseUiaProvider(HWND handle)
{
	if (!OsVersion.IsWindows8OrGreater())
	{
		return;
	}
	for (int i = 0; i < Items.Count; i++)  // <--- THIS ITERATES OVER ALL INDICES IN THE VIRTUAL LIST VIEW
	{
		Items.GetItemByIndex(i)?.ReleaseUiaProvider(); // <--- THIS INVOKES OnRetrieveVirtualItem(e)
	}
	if (_defaultGroup != null)
	{
		DefaultGroup.ReleaseUiaProvider();
	}
	foreach (ListViewGroup group in Groups)
	{
		group.ReleaseUiaProvider();
	}
	foreach (ColumnHeader column in Columns)
	{
		column.ReleaseUiaProvider();
	}
	base.ReleaseUiaProvider(handle);
}

After this super expensive retrieval, ListViewItem then does nothing

internal void ReleaseUiaProvider()
{
	if (!IsAccessibilityObjectCreated)  // <--- RETURNS FALSE
	{
		return;
	}
	if (OsVersion.IsWindows8OrGreater())
	{
		if (_accessibilityObject is ListViewItemBaseAccessibleObject listViewItemBaseAccessibleObject)
		{
			listViewItemBaseAccessibleObject.ReleaseChildUiaProviders();
		}
		PInvoke.UiaDisconnectProvider(_accessibilityObject, skipOSCheck: true);
	}
	_accessibilityObject = null;
}

Possible Resolutions

  • Make SupportsUiaProviders protected overrideable.
  • Move the test if (!IsAccessibilityObjectCreated) higher up in the call hierachy.
  • Invoke the ReleaseUiaProvider() only for really created ListViewItems.

Disclaimer
I'm not profient with UI Automation, so I do not know, what a proper implementation should excatly look like.
In my case I do not use this feature explicitly.

Steps to reproduce

An example is difficult to post.
Please see the analysis above.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions