Repository: dotnet/maui
File: src/Essentials/src/SemanticScreenReader/SemanticScreenReader.windows.cs (C#)

Code before:
﻿using Microsoft.Maui.ApplicationModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Media;

namespace Microsoft.Maui.Accessibility
{
	partial class SemanticScreenReaderImplementation : ISemanticScreenReader
	{
		public void Announce(string text)
		{
			if (WindowStateManager.Default.GetActiveWindow() is not Window window)
				return;

			var peer = FindAutomationPeer(window.Content);

			// This GUID correlates to the internal messages used by UIA to perform an announce
			// You can extract it  by using accessibility insights to monitor UIA events
			// If you're curious how this works then do a google search for the GUID
			peer.RaiseNotificationEvent(
				AutomationNotificationKind.ActionAborted,
				AutomationNotificationProcessing.ImportantMostRecent,
				text,
				"270FA098-C644-40A2-A0BE-A9BEA1222A1E");
		}

		// This isn't great but it's the only way I've found to announce with WinUI.
		// You have to locate a control that has an automation peer and then use that
		// to perform the announce operation. This creates scenarios where the
		// screen might not have any automation peers on it to use but in those cases
		// you really shouldn't be using the announce API
		static AutomationPeer FindAutomationPeer(DependencyObject depObj)
		{
			if (depObj != null)
			{
				for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
				{
					DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
					if (child is UIElement element && FrameworkElementAutomationPeer.FromElement(element) != null)
					{
						return FrameworkElementAutomationPeer.FromElement(element);
					}

					var childItem = FindAutomationPeer(child);
					if (childItem != null)
						return childItem;

Review comment (bug):
This doesn’t exactly fix the issue in question, right?

We’re just avoiding the crash when window.Content is null. But this ends up silently skipping the announcement if the visual tree isn’t ready. Now the user gets no feedback, the announcement just doesn’t happen. No crash, no warning, just silence.

From what I understand, on Windows, we need to locate a control with an automation peer to perform the announce operation. If that’s the expected behavior, maybe we shouldn’t silently skip here.

Instead, could we consider throwing a meaningful exception with a helpful message, so users know they’re calling Announce() too early and can fix it by moving the call to a better place?

Code after:
﻿using System.Diagnostics;
using Microsoft.Maui.ApplicationModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Media;

namespace Microsoft.Maui.Accessibility
{
	partial class SemanticScreenReaderImplementation : ISemanticScreenReader
	{
		public void Announce(string text)
		{
			if (WindowStateManager.Default.GetActiveWindow() is not Window window)
				return;

			if (window.Content is null)
			{
				// If the window content is null, we can't announce anything yet.
				// This can happen if the app is still starting up.
				Debug.WriteLine("SemanticScreenReader.Announce() was called too early. The window content is not yet initialized, announcement skipped.");
				return;
			}
			var peer = FindAutomationPeer(window.Content);

			// This GUID correlates to the internal messages used by UIA to perform an announce
			// You can extract it  by using accessibility insights to monitor UIA events
			// If you're curious how this works then do a google search for the GUID
			peer.RaiseNotificationEvent(
				AutomationNotificationKind.ActionAborted,
				AutomationNotificationProcessing.ImportantMostRecent,
				text,
				"270FA098-C644-40A2-A0BE-A9BEA1222A1E");
		}

		// This isn't great but it's the only way I've found to announce with WinUI.
		// You have to locate a control that has an automation peer and then use that
		// to perform the announce operation. This creates scenarios where the
		// screen might not have any automation peers on it to use but in those cases
		// you really shouldn't be using the announce API
		static AutomationPeer FindAutomationPeer(DependencyObject depObj)
		{
			if (depObj != null)
			{
				for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
				{
					DependencyObject child = VisualTreeHelper.GetChild(depObj, i);