我有一个需要支持屏幕阅读器(尤其是 JAWS)的 WPF 应用程序。问题是,当列表视图项目发生更改(添加、删除)时,JAWS 不会宣布任何内容。而盲人用户完全不知道发生了什么。在尝试从列表视图控件中添加/删除项目时,我有什么方法可以强制屏幕阅读器宣布一些文本?我该怎么做?
3 回答
如果JAWS
读者不支持该功能,可以自行实现SpeechSynthesizer
。语音播放示例:
using System.Speech.Synthesis;
SpeechSynthesizer MySpeechSynthesizer = new SpeechSynthesizer();
MySpeechSynthesizer.Speak("Hello!");
我使用了一个ObservableCollection
被赋值的例子ListBox
。ObservableCollection
是一个事件,其中包含对集合[MSDN]CollectionChanged
执行的操作的枚举:
Member name Description
------------ ------------
Add One or more items were added to the collection.
Move One or more items were moved within the collection.
Remove One or more items were removed from the collection.
Replace One or more items were replaced in the collection.
Reset The content of the collection changed dramatically.
这event
将像这样实现:
// Set the ItemsSource
SampleListBox.ItemsSource = SomeListBoxCollection;
// Set handler on the collection
SomeListBoxCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(SomeListBoxCollection_CollectionChanged);
private void SomeListBoxCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// Some actions, in our case - speech
}
}
下面是我的例子:
XAML
<Window x:Class="JAWShelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<ListBox Name="MyListBox" DisplayMemberPath="Name" SelectedIndex="0" Width="100" Height="100" Loaded="MyListBox_Loaded" />
<WrapPanel Width="200" Height="30" Margin="40,150,0,0">
<Button Name="AddButton" Padding="5" Content="Add item" VerticalAlignment="Bottom" Click="AddButton_Click" />
<Button Name="RemoveButton" Padding="5" Margin="30,0,0,0" Content="Remove item" VerticalAlignment="Bottom" Click="RemoveButton_Click" />
</WrapPanel>
</Grid>
</Window>
Code behind
// using System.Speech.Synthesis;
// using System.Collections.ObjectModel;
// using System.Collections.Specialized;
public partial class MainWindow : Window
{
public class Person
{
public string Name
{
get;
set;
}
}
private ObservableCollection<Person> DataForListBox = new ObservableCollection<Person>();
public MainWindow()
{
InitializeComponent();
}
private void MyListBox_Loaded(object sender, RoutedEventArgs e)
{
DataForListBox.Add(new Person()
{
Name = "Peter Orange",
});
MyListBox.ItemsSource = DataForListBox;
DataForListBox.CollectionChanged += new NotifyCollectionChangedEventHandler(DataForListBox_CollectionChanged);
}
private void DataForListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
SpeechSynthesizer MySpeechSynthesizer = new SpeechSynthesizer();
MySpeechSynthesizer.Speak("You are add item.");
}
if (e.Action == NotifyCollectionChangedAction.Remove)
{
SpeechSynthesizer MySpeechSynthesizer = new SpeechSynthesizer();
MySpeechSynthesizer.Speak("You are remove item.");
}
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
DataForListBox.Add(new Person()
{
Name = "Jack Rider",
});
}
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
DataForListBox.RemoveAt(1);
}
}
没有问题,您可以添加项目的复制文本Add/Remove
。您还可以.wav
使用以下命令添加播放文件PromptBuilder
:
PromptBuilder MyPromptBuilder = new PromptBuilder();
MyPromptBuilder.AppendAudio("SomeFile.wav");
JAWS 只会响应获得焦点的控件。我在我的应用程序中需要类似的功能,并通过以下方式解决了它。
将两个隐藏的文本框控件添加到您的布局中。
<!--Controls used to announce accessibility messages for screen readers.--> <TextBox x:Name="ATMessage_Silent" Height="1" Width="1" IsTabStop="False" AutomationProperties.Name=" "/> <TextBox x:Name="ATMessage_Audible" Height="1" Width="1" IsTabStop="False"/>
添加一个类来宣布消息。我发现为了使其可靠,我需要在多个控件之间传递焦点之间短暂暂停。否则 JAWS 不能可靠地宣布消息。
public class AccessibilityMessage { private AccessibilityMessage(object sender, string message, double delay) { DispatcherTimer sleep = new DispatcherTimer(); int counter = 3; try { if (accessibilityMessageAudibleControl != null && accessibilityMessageSilentControl != null) { sleep.Interval = TimeSpan.FromMilliseconds(delay); // Update the message. accessibilityMessageAudibleControl.SetValue(AutomationProperties.NameProperty, message); // Give focus to the silent control. accessibilityMessageSilentControl.IsTabStop = true; accessibilityMessageSilentControl.Focus(); // Give focus to the message. accessibilityMessageAudibleControl.IsTabStop = true; accessibilityMessageAudibleControl.Focus(); // Use a timer to simulate a sleep. We need to pause briefly to give enough time // for the screen reader to process the focus on the message control. After a brief // pause we will give focus back to the original control. If we do not pause like // this the screen reader will not reliably react to the message. sleep.Tick += (s, e) => { counter--; // Check to see if it is time to focus the original control. if (counter == 0) { // Return focus to the original control that triggered the message. if (sender != null && sender is Control) { // Give focus back to the original control. ((Control)sender).Focus(); } // Exit the timer. sleep.Stop(); // Inform any listeners the message has been announced. if (Announced != null) Announced(this, null); } }; // Start the time. sleep.Start(); } else { throw new Exception("Accessibility message controls are not defined in the Application Manager. Unable to announce accessibility message."); } } catch (Exception ex) { ErrorDialog.Show(ex, sender); } } public event EventHandler Announced; public static AccessibilityMessage Announce(object sender, string message, double delay = 250) { return new AccessibilityMessage(sender, message, delay); } }
宣布您的消息。您可以简单地发布公告,也可以使用 Announced 事件发布公告,然后在发布公告后执行其他工作。
在数据网格加载数据时发布通知以通知用户请稍候。
// Pass myGrid as the sender so it will receive focus after the announcement. ApplicationManager.AccessibilityMessage.Announce(myGrid, "Loading purchase orders table, please wait.").Announced += (s, arg) => { // MAKE WEB SERVICE CALL TO RETRIEVE DATA. DataService svc = new DataService(); svc.ListPurchasOrdersCompleted += OnListPurchaseOrders_Completed(); svc.ListPurchaseOrders(); };
宣布数据已加载到数据网格中。
private void OnListPurchaseOrders_Completed(object sender, AsyncCompletedEventArgs e) { try { if (e.Error == null) { myGrid.ItemsSource = e.Result(); // Pass myGrid as the sender so it will receive focus after the announcement. AccessibilityMessage.Announce(myGrid, string.Format("Loaded {0} orders into the purchase orders table.", myGrid.Items.Count)); } else { throw e.Error; } } catch (Exception ex) { ErrorDialog.Show(ex, this); } }
使用它,您只需使用 Announce() 调用即可随时发布公告。我最初是为 Silverlight 实现的。它也应该适用于 WPF。
我喜欢这样做的方式是在 UI 中有一个高度为 0 的 TextBlock:
<TextBlock
x:Name="screenReaderText"
Height="0"
AutomationProperties.LiveSetting="Assertive" />
然后我使用这个方法让它读取一些东西:
public void ReadTextToScreenReader(string text)
{
var peer = UIElementAutomationPeer.FromElement(this.screenReaderText);
if (peer != null)
{
this.screenReaderText.Text = text;
peer.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged);
}
}
基本上它是在说“看,这个控件改变了内容,最好把它读出来。”