前言
Stylet是我最近很喜欢使用的一个WPF框架,它的很多设计都体现了约定优于配置的思想。因此你会发现使用它非常方便,几乎不需要任何配置,开箱即用,只需知道它的一些约定即可。
查看Samples中Hello这个例子,只要在xaml中这样写:
xaml
<Button Command="{s:Action SayHello}">Say Hello</Button>
然后你点击这个按钮,就会触发对应ViewModel中的SayHello方法,使用起来非常简单方便。这背后Stylet框架做了什么呢?让我们揭开它的神秘面纱吧!!
ActionExtension
先来看看ActionExtension
,它位于Stylet.Xaml
命名空间:
s:Action
的第一阶段是XAML解析,这个阶段的核心任务是将XAML标记中的{s:Action SayHello}
语法翻译成CommandAction
实例。这个过程由ActionExtension
标记扩展类完成,它是整个命令绑定系统的入口点。
当XAML解析器遇到{s:Action SayHello}
时,会调用ActionExtension.ProvideValue
方法:
HandleDependencyObject
方法根据目标属性的类型进行分支处理:
对于命令绑定场景,CreateCommandAction
方法创建CommandAction
实例:
rootObject
就是具有s:Action
的页面:
View.ActionTarget
现在我们已经找到了View
,但是想要触发的方法是在ViewModel
上的,那么就要想办法找到对应的ViewModel
,Stylet
中是通过View.ActionTarget
这个附加属性实现的。通过View.ActionTarget
附加属性将ViewModel
注入到可视化树中,使得后续阶段能够找到正确的命令执行目标。
先来看看View.ActionTarget
的定义,位置在命名空间Stylet.Xaml
下的View
类中:
默认值:InitialActionTarget
(一个特殊的标记对象)
继承性:FrameworkPropertyMetadataOptions.Inherits
确保属性值可以沿着可视化树向下传递
当View.Model
属性被设置时,会触发PropertyChangedCallback
,通过ViewManager
建立视图和ViewModel
的关联。
在将View
与ViewModel
关联起来的时候,设置了当前View
的ActionTarget
为对应的ViewModel
。
CommandAction
在CreateCommandAction
方法中会返回一个CommandAction
。
这里是在把当前这个Subject
对象里的ActionTargetProperty
的值'拴'到 某个尚未显式的目标属性上,而且只让它从源(Subject)流向目标,不会反向同步。
这里的Subject
是Button
对象,为什么也能找到ActionTargetProperty
这个属性呢?
由于ActionTargetProperty
设置了FrameworkPropertyMetadataOptions.Inherits
标志,这个属性会自动沿着可视化树向下传播:
csharp
Window (View.ActionTargetProperty)
├── UserControl (继承Window的View.ActionTargetProperty)
│ ├── Button (继承UserControl的View.ActionTargetProperty)
│ └── TextBox (继承UserControl的View.ActionTargetProperty)
└── Grid (继承Window的View.ActionTargetProperty)
但是现在还没完成绑定,只是绑定的一端,还需要设置将这个属性绑定到哪里。
BindingOperations.SetBinding(this, targetProperty, multiBinding);
将ActionBase
的targetProperty
依赖属性绑定到View.ActionTargetProperty
。
你会发现现在并没有绑定到ViewModel
,只是View.ActionTargetProperty
的默认值:
由于targetProperty
依赖属性从null
到View.InitialActionTarget
也会触发UpdateActionTarget
,不过这一次不做任何处理,直接返回:
当设置View.ActionTargetProperty
的值为对应的ViewModel
时:
就又会触发UpdateActionTarget
方法,这一次拿到了对应的ViewModel
:
拿到ViewModel
上的方法:
拿到是否可以执行对应命令的属性:
现在重点来关注一下这里:
csharp
Expressions.ConstantExpression targetExpression = Expressions.Expression.Constant(newTarget);
Expressions.MemberExpression propertyAccess = Expressions.Expression.Property(targetExpression, guardPropertyInfo);
this.guardPropertyGetter = Expressions.Expression.Lambda<Func<bool>>(propertyAccess).Compile();
这段代码在运行时动态编译一段表达式树
,生成一个无参、返回 bool
的委托(Func<bool>)
,用来快速读取某个对象的布尔型属性值。
第一行相当于就是使用这个newTarget
对象,第二行就是访问这个newTarget
对象的CanSayHello
属性,相当于newTarget.CanSayHello
。
Expression.Lambda<Func<bool>>
将表达式树包装成一个无参Lambda
表达式(形如 () => newTarget.CanSayHello
)。
.Compile()
把表达式树编译成可执行IL ,生成一个静态缓存的委托Func<bool>
。
然后调用this.UpdateCanExecute();
触发CanExecuteChanged
事件,就会执行CanExecute
方法,在这个方法中就使用到了刚刚生成的获取newTarget.CanSayHello
属性的委托:
现在是false
,命令无法执行:
当我输入小明
的时候就修改了这个属性值,就会触发CanExecuteChanged
事件:
就又会调用刚刚生成的那个获取newTarget.CanSayHello
属性的委托:
这一次返回true
,命令可以执行。
当我点击按钮的时候,如果命令可以执行,就会执行Execute
方法
这样就会执行ViewModel
中对应的方法。
以上就是个人对于WPF Stylet中Command="{s:Action SayHello}"
的设计与实现的理解。