上一篇我们提到如何让“讲述人”读出自定义的CanReadGrid,但“讲述人”仍然无法识别CanReadGrid上绑定的Command。XAML代码如下:
<StackPanel> <TextBlock Text="{x:Bind Title,Mode=OneWay}" Foreground="White"></TextBlock> <local:CanReadGrid Background="Red" AutomationProperties.Name="Can read gird" Height="100"> <Interactivity:Interaction.Behaviors> <Core:EventTriggerBehavior EventName="Tapped"> <Core:InvokeCommandAction Command="{x:Bind ChangeTitleCommand}"/> </Core:EventTriggerBehavior> </Interactivity:Interaction.Behaviors> </local:CanReadGrid> </StackPanel>
我们可以看到通过Behaviors绑定了Command,在Tapped事件发生时触发ChangeTitleCommand。
我们再来对比一下系统控件Button的写法:
<Button Command="{x:Bind ChangeTitleCommand}">I am Button</Button>
在“讲述人”模式下,点击上面这个Button按钮,“讲述人”除了会念出“I am Button Button.”这句话以外,还会补充一句“Double tap to activate.”这时双击Button将会触发ChangeTitleCommand。
其中不一样的地方无非就是Button自带有名为“Command”的,类型为ICommand的依赖属性(Dependency Property):
public System.Windows.Input.ICommand Command { get; set; } Member of Windows.UI.Xaml.Controls.Primitives.ButtonBase
而我们自定义的CanReadGrid,则是通过附加属性(Attached Property)来获得绑定Command的能力。
附件属性也是一种特殊的依赖属性,二者殊归同路。既然Button通过依赖属性可以做到的事情,附加属性一样可以完成。
想要弄明白Button的Command是如何被调用的,最简单的办法就是去查看源码呗:
public class ButtonAutomationPeer : ButtonBaseAutomationPeer, IInvokeProvider { /// <summary>Initializes a new instance of the <see cref="T:System.Windows.Automation.Peers.ButtonAutomationPeer" /> class.</summary> /// <param name="owner">The element associated with this automation peer.</param> public ButtonAutomationPeer(Button owner) : base(owner) { } /// <summary>Gets the name of the control that is associated with this UI Automation peer.</summary> /// <returns>A string that contains "Button".</returns> protected override string GetClassNameCore() { return "Button"; } /// <summary>Gets the control type of the element that is associated with the UI Automation peer.</summary> /// <returns> /// <see cref="F:System.Windows.Automation.Peers.AutomationControlType.Button" />.</returns> protected override AutomationControlType GetAutomationControlTypeCore() { return AutomationControlType.Button; } /// <summary>Gets the object that supports the specified control pattern of the element that is associated with this automation peer.</summary> /// <returns>If <paramref name="patternInterface" /> is <see cref="F:System.Windows.Automation.Peers.PatternInterface.Invoke" />, this method returns a this pointer, otherwise this method returns null.</returns> /// <param name="patternInterface">A value in the enumeration.</param> public override object GetPattern(PatternInterface patternInterface) { if (patternInterface == PatternInterface.Invoke) { return this; } return base.GetPattern(patternInterface); } /// <summary>This type or member supports the Windows Presentation Foundation (WPF) infrastructure and is not intended to be used directly from your code.</summary> void IInvokeProvider.Invoke() { if (!base.IsEnabled()) { throw new ElementNotEnabledException(); } base.Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(delegate(object param) { ((Button)base.Owner).AutomationButtonBaseClick(); return null; }), null); } }
果不其然发现了上一篇我们提到的GetClassNameCore,GetAutomationControlTypeCore,GetPattern三个方法。另外还有一个奇怪的void IInvokeProvider.Invoke()。这货看名字也能猜出来是干啥的啦,这货竟然去调了Button类里的Click方法……
知道真相的我眼泪流出来……搞啥呢,那我在这里调一下Command.Execute不就成了!
首先我们给CanReadGrid添加ExecuteCommand方法,该方法通过DependencyObject的GetValue方法一层层拿到Command,然后执行Execute。
public class CanReadGrid : Grid { protected override AutomationPeer OnCreateAutomationPeer() { return new GridAutomationPeer((this)); } public void ExecuteCommand() { var behaviors = Interaction.GetBehaviors(this); var actions = behaviors[0].GetValue(EventTriggerBehavior.ActionsProperty) as ActionCollection; var command = actions[0].GetValue(InvokeCommandAction.CommandProperty) as ICommand; command.Execute(null); } }
第二步就是完善GridAutomatioPeer,这里需要注意的是IInvokeProvider这个接口,通过Button的源码推测具有Action的控件需要实现这个接口的Invoke方法来执行操作。我们也是在Invoke方法里来调用CanReadGrid类里的ExecuteCommand方法。
public class GridAutomationPeer : FrameworkElementAutomationPeer, IInvokeProvider { public GridAutomationPeer(Grid owner) : base(owner) { } public async void Invoke() { await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { ((CanReadGrid)base.Owner).ExecuteCommand(); }); } protected override object GetPatternCore(PatternInterface patternInterface) { if (patternInterface == PatternInterface.Invoke) { return this; } return null; } }
大功告成!开启“讲述人”模式来验证成果吧!
结尾插播个广告,本篇内容100%原创,当时俺翻烂了Google的搜索页面、中英文各种blog也木有讲如何让“讲述人”调用Command,逼的俺自由发挥啊。特地写了这篇造福全人类,各位不要吝啬点个推荐哦,虽然“讲述人”并没有什么卵用……