WPF,MVVM,如何让屏幕阅读器宣布新添加的列表框项目

问题描述 投票:0回答:1

我正在修改现有的 WPF、MVVM 应用程序(使用 .NET 8),以便它可以支持屏幕阅读器。为了方便、快速地进行测试,我使用 Windows 11 Narrator,但也将使用 JAWS 进行测试。

一般来说,这是可行的,但是我遇到了一个问题,即当将新项目添加到

ListBox
,或者添加到绑定到
ObservableCollection
ListBox
时,屏幕阅读器不会宣布新添加的项目。

这对于我们的视障用户来说是一个相当大的问题,因为动态添加到列表中的项目非常重要并且需要注意。从有视力的用户的角度来看,他们可以立即看到新添加的项目,而视障用户不会收到来自屏幕阅读器的通知。

我已经成功地在小型测试应用程序上获取屏幕阅读器通知,方法是将

AutomationProperties.LiveSetting
添加到 XAML 中的
Listbox
,检索
AutomationPeer
ListBox
,并在添加项目时提升
AutomationEvents.LiveRegionChanged
:

// Do some processing which adds a new item, then retrieve the AutomationPeer to raise the event
var peer = UIElementAutomationPeer.FromElement(MyListBox);
if (peer != null)
{
  peer.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged);
}

这在我的测试应用程序中效果很好(使用隐藏代码),屏幕阅读器会通知用户新添加的项目。

这很容易在使用隐藏代码的小型测试应用程序中实现。我想要弄清楚的是如何使用 MVVM 来实现这一点,并希望有人能为我指出正确的方向。

使用 MVVM,我在视图模型中有一个

ObservableCollection
。那么,当将一个项目添加到
ObservableCollection
时,如何获取
AutomationPeer
ListBox
并在我的视图模型中引发自动化事件?

也许我尝试以错误的方式解决这个问题,我对 WPF 和 MVVM 都很陌生。

这是我的例子:

XAML

<Window x:Class="WpfAppAccessibilityTest.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"
        xmlns:local="clr-namespace:WpfAppAccessibilityTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ListBox AutomationProperties.LiveSetting="Assertive" AutomationProperties.Name="My list of things" ItemsSource="{Binding ListOfStuff}"/>
    </Grid>
</Window>

Code Behind

using System.Collections.ObjectModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WpfAppAccessibilityTest
{
    public class MyViewModel
    {
        Dispatcher dispatcher;

        public MyViewModel()
        {
            dispatcher = Dispatcher.CurrentDispatcher;

            // Simulate handling some event that occurs in the future
            Task.Delay(5000).ContinueWith(_ =>
            {
                dispatcher.Invoke(() =>
                    ListOfStuff.Add("Item2")
                    // How do I raise an AutomationPeer event here so screen reader will anounce the newly added item?
                );
            });
        }

        public ObservableCollection<string> ListOfStuff { get; } = new ObservableCollection<string>() { "Item1" };

    }

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

            DataContext = new MyViewModel();
        }
    }
}

我已经检查了这个问题,但没有一个答案似乎合适 - 我可以让屏幕阅读器宣布,我只是不知道如何从视图模型中做到这一点。

c# wpf mvvm listbox screen-readers
1个回答
0
投票

只是回答我自己的问题,希望其他人可能会发现它有用。

我通过代码隐藏解决了这个问题,而不是在视图模型中。为此,您需要确保为

ListBox
指定一个名称,以便您可以从后面的代码访问它。

因为

ListBox
控件没有明显的变化事件。我发现我可以将
ListBox.Items
转换为
INotifyCollectionChanged
,这使我可以访问
CollectionChanged
事件。一旦我获得该事件,就很容易获得
AutomationPeer
并触发
LiveRegionChanged
事件。

为了避免答案过于复杂,我只包含了视图详细信息,因为视图模型没有更改:

XAML

<Window x:Class="WpfAppAccessibilityTest.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"
        xmlns:local="clr-namespace:WpfAppAccessibilityTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <!--Ensure you give the list a name so you can access it in code behind-->
        <ListBox Name="MyListBox" AutomationProperties.LiveSetting="Assertive" AutomationProperties.Name="My list of things" ItemsSource="{Binding ListOfStuff}"/>
    </Grid>
</Window>

Code Behind

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new MyViewModel();

            if (MyListBox.Items is INotifyCollectionChanged notifyList)
            {
                notifyList.CollectionChanged += NotifyList_CollectionChanged;
            }
        }

        private void NotifyList_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Remove:
                    var peer = UIElementAutomationPeer.FromElement(MyListBox);
                    peer?.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged);
                    break;
                default:
                    break;
            }
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.