我创建了一个UWP自定义(即模板化)控件。如何使其可访问?
现在,当我尝试将它与Windows讲述人工具一起使用时,它表现不佳。有时讲述者根本看不到它,有时它会在我不想要的时候深入到我控制范围内的UI元素树中。
最初我认为我只需要设置一些自动附加属性,但它们没有明显效果。
答案 0 :(得分:6)
当我发现AutomationPeer对象时,我得到了这个。基本上每个控件类都需要一个关联的AutomationPeer类,它将特定控件的行为映射到可访问性工具要使用的一组标准。
为了简单起见,我创建了一个简单的AccessibleButton类,它直接从Control派生。 (如果你真的在制作一个按钮,你可能想要从Button或ButtonBase派生,但是已经有了一个相关的AutomationPeer类。我只是为了解释目的而这么做。)
这是Generic.xaml代码:
<Style TargetType="local:AccessibleButton">
<Setter Property="UseSystemFocusVisuals" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AccessibleButton">
<Grid Background="{TemplateBinding Background}">
<Border BorderThickness="10">
<TextBlock x:Name="Name" Text="{TemplateBinding Label}"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
这是背后的代码:
public sealed class AccessibleButton : Control
{
public AccessibleButton()
{
this.DefaultStyleKey = typeof(AccessibleButton);
}
public static DependencyProperty LabelProperty = DependencyProperty.Register(
"Label", typeof(string), typeof(AccessibleButton),
PropertyMetadata.Create(string.Empty));
public string Label
{
set { SetValue(LabelProperty, value); }
get { return (string)GetValue(LabelProperty); }
}
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
Click?.Invoke(this, EventArgs.Empty);
}
public event EventHandler Click;
}
MainPage.xaml中的一些示例用法:
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:AccessibleButton Background="Red" Label="Click Me!" Click="AccessibleButton_Click"/>
<local:AccessibleButton Background="Green" Label="No, Click Me!" Click="AccessibleButton_Click"/>
<local:AccessibleButton Background="Blue" Label="Ignore them, Click Me!" Click="AccessibleButton_Click"/>
</StackPanel>
如果您在每个按钮上运行讲述人和鼠标,您会发现它忽略了按钮的边框(您只需获得一点点击声)。如果将鼠标悬停在文本上,它有时会读取整个文本,但通常会读取单个单词以及文本末尾的神秘“空行”。我猜测TextBlock控件将其输入分解为每个单词的单个对象......
非常糟糕。
修复程序是AutomationPeer。这是基础课:
public class AccessibleButtonAutomationPeer : FrameworkElementAutomationPeer
{
public AccessibleButtonAutomationPeer(FrameworkElement owner): base(owner)
{
}
}
(FrameworkElementAutomationPeer位于Windows.UI.Xaml.Automation.Peers名称空间中。)
在AccessibleButton类中添加此覆盖以创建它:
protected override AutomationPeer OnCreateAutomationPeer()
{
return new AccessibleButtonAutomationPeer(this);
}
这没有任何用处。我们需要添加一些方法。首先让我们通过实现GetChildrenCore()方法阻止屏幕阅读器看到我们按钮的内容:
protected override IList<AutomationPeer> GetChildrenCore()
{
return null;
}
如果你只是这样跑,你会发现它现在没什么作用。如果一个控件确实得到了焦点,它只会说“自定义”。我们可以通过实现GetNameCore()方法来说明控件标签中的文本:
protected override string GetNameCore()
{
return ((AccessibleButton)Owner).Label;
}
这有帮助。现在,只要我们选择一个控件,它就会说出按钮标签文本。但它最后仍然说“自定义”。要解决这个问题,我们必须通过实现GetAutomationControlTypeCore()方法来告诉系统它是什么类型的控件来指示控件是一个按钮:
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Button;
}
现在它会说按钮标签后跟“按钮”。
这实际上很有用!
视力不佳但能够通过鼠标或触摸操作屏幕的用户至少知道这些按钮上的标签是什么。
AutomationPeers还允许您通过实现模式提供程序接口来支持“交互模式”。对于此按钮示例,Invoke模式是合适的。我们需要实现IInvokeProvider,所以将它添加到类声明中:
public class AccessibleButtonAutomationPeer :
FrameworkElementAutomationPeer,
IInvokeProvider
(IInvokeProvider正在使用Windows.UI.Xaml.Automation.Provider命名空间。)
然后重写GetPatternCore以表明它受支持:
protected override object GetPatternCore(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.Invoke)
{
return this;
}
return base.GetPatternCore(patternInterface);
}
实现IInvokeProvider.Invoke方法:
public void Invoke()
{
((AccessibleButton)Owner).DoClick();
}
(为了支持这一点,我将AccessibleButton.OnPointerPressed方法的主体移动到它自己的DoClick()方法中,以便我可以从这里调用它。)
要测试此项,请使用旁白选择按钮,然后按Caps Lock + Space以调用控件的默认功能。它将使用Invoke方法调用DoClick()。
AutomationPeer类支持比这更多的功能。如果我开始实施它,我将更新这篇文章的更多细节。