Making a grouped WPF ItemsControl accessible to screen readers

Viewed 153

I have an ItemsControl in my WPF application. This application must be accessible to screen reader software such as Narrator, NVDA and JAWS.

The problem is that when using grouping, child items within that ItemsControl suddenly become invisible to screen readers. If I remove the grouping, they appear.

How can I have grouping and visibility of items to screen readers?

1 Answers

WPF UI elements can expose "automation peers" to screen readers.

By default, WPF does not ship a peer for ItemsControl, though it does for derived controls such as ListBox.

To get around this you can create your own derived control and have it return an automation peer.

using System.Windows.Automation.Peers;
using System.Windows.Controls;

public sealed class AccessibleItemsControl : ItemsControl
{
    protected override AutomationPeer OnCreateAutomationPeer()
    {
        return new AccessibleItemsControlAutomationPeer(this);
    }

    private sealed class AccessibleItemsControlAutomationPeer : ItemsControlAutomationPeer
    {
        public AccessibleItemsControlAutomationPeer(ItemsControl owner)
            : base(owner)
        {
        }

        protected override ItemAutomationPeer CreateItemAutomationPeer(object item)
        {
            return new AccessibleItemAutomationPeer(item, this);
        }

        protected override string GetClassNameCore() => "AccessibleItemsControl";

        protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.List;
    }

    private sealed class AccessibleItemAutomationPeer : ItemAutomationPeer
    {
        public AccessibleItemAutomationPeer(object item, ItemsControlAutomationPeer itemsControlAutomationPeer)
            : base(item, itemsControlAutomationPeer)
        {
        }

        protected override string GetClassNameCore() => "AccessibleItemsControlItem";

        protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.DataItem;
    }
}

You can specify the automation name of items and groups in XAML with code like this:

<controls:AccessibleItemsControl>
  <ItemsControl.ItemContainerStyle>
    <Style>
      <!-- customise the binding path here to suit your application -->
      <Setter Property="AutomationProperties.Name" Value="{Binding Path=MyDisplayName}" />
    </Style>
  </ItemsControl.ItemContainerStyle>
  <ItemsControl.GroupStyle>
    <GroupStyle>
      <GroupStyle.ContainerStyle>
        <Style>
          <!-- customise the binding path here to suit your application -->
          <Setter Property="AutomationProperties.Name" Value="{Binding Path=(CollectionViewGroup.Name).MyDisplayName}" />
        </Style>
      </GroupStyle.ContainerStyle>
    </GroupStyle>
  </ItemsControl.GroupStyle>
</controls:AccessibleItemsControl>
Related