解析Caliburn.Micro(四)
书接前文,继续来介绍一下Caliburn.Micro(CM)中的Convention。
前言
Caliburn.Micro这个系列也写了好几篇了,作为一个第三方的应用框架,关于细节的详尽介绍并不是第一位的,能快而准确的把握它的整体思路才是最重要的。Caliburn.Micro框架中包含了很多功能,有前面介绍过的Action,Convention,Coroutine(协同),EventAggregator(弱事件)等等。其中很多功能都是锦上添花之作,比如EventAggregator等,在Prism等框架中都有介绍,CM的核心思路是它的Action以及Convention,详细的说一下这条主线。
CM主线
Caliburn.Micro的定位是一个轻量级的MVVM框架。作为MVVM,View和ViewModel之间用DataContext关联,View通过Binding和Command把操作传递给ViewModel并根据ViewModel中绑定属性的变化来自动刷新。用一个表格来对比一下Caliburn.Micro和通用MVVM框架的不同:
特性 | 普通MVVM框架 | Caliburn.Micro |
自动关联View和ViewModel(通过View的DataContext)。 | 不支持 | 支持,使用NameTransformer |
Command | 支持,推荐使用Command | 不支持,使用Action作为替代方案。 |
Binding | 支持,推荐使用Binding。 | 支持,可以使用Convention作为替代方案。 |
Validation,StringFormat,Converter,UpdateSourceTrigger | 支持,使用Binding的对应机制 | 支持,可以使用Convention作为替代方案。 |
举个简单的例子来对比:
普通MVVM框架:
使用Caliburn.Micro的框架
对应的ViewModel:
使用了Caliburn.Micro后,只需要设置Button和TextBox的Name,使它匹配到ViewModel对应属性和方法的名字,CM会自动设置绑定以及Button方法的调用。
这个是CM设计的一个本意,使View和ViewModel之间的调用更加简单,只需要设定好对应的名字,其他的由CM来搞定。
Command与Action
WPF中定义了Command机制,接口是ICommand,有Execute和CanExecute两个方法,并且提供了CommandParameter作为调用参数。使用Command模式,可以把执行操作的对象和处理操作的对象进行解耦,WPF这个思路是很好的,在具体实现上还存在一些不足。
WPF中一些控件提供了Command属性,允许你加入具体操作的ICommand。Command的触发是基于Routed Event的,比如Button就是在OnClick时触发了Command的执行。WPF在很多控件上都实现了Command,比如Button等,但还不够完全,像ComboBox等控件就没有Command。
一个Command只能响应一个Routed Event事件,对于默认的控件,比如Button等,它的响应事件是写死的(Click)。WPF在控件上提供了CommandBindings允许添加多个Command,但使用CommandBindings的写法很繁琐,对于MVVM模式的支持也不是太好。为了更好的支持MVVM模式,Blend提出了TriggerAction来更简单的添加Command,如:
这里指明了Command的触发前提是Click事件发生,触发后执行对应ViewModel的LoginCommand。
CM的Action就是基于Blend的TriggerAction,它的ActionMessage继承自TriggerAction<FrameworkElement>,不同之处在于它没有调用ViewModel中的Command,而是直接用反射拿到了对应ViewModel中的方法,去执行方法了。
Action与Convention
为了使用简单,CM中的Action可以不指定方法的名字,它会通过控件的名字(Name)来和ViewModel中的属性、方法进行匹配。关于Action的具体使用,请参见第一篇文章。我们讨论框架的设计,这个设计不会凭空出现,设计出了ActionMessage来替代Command,我们来看看接下来会出现的问题。
- CM在Action的设计处采用了一种傻瓜式的操作思路,只需要你设计控件的名字(Name),其他的都用智能匹配帮你解决。既然是智能的模式,那这个智能就是控制点,控件的哪些属性需要智能使用绑定,如何保证这个智能性足够智能,使用了它并不会影响老的设计实现。
- WPF中的Command和Binding都是它的亮点,使用了CM,代码简洁了,Command和Binding都不见了。如何保证原有的一些功能:Command的CanExecute判断,Bindng的Validation,StringFormat,Converter等等,
- 复杂性与灵活性,功能强大但是代码众多的框架是让人望而生畏的,简洁明快但是缺乏扩展的框架也是不让人满意的,如何达到这样一个平衡性也是一个挑战。
为了解决这些问题,CM引入了Convention这个概念。
ElementConvention
关于如何自动匹配View和ViewModel,请参见前文。这里主要来介绍ElementConvention,简略来说,它是为了解决智能性而推出的,是指导具体类型Convention的处理描述。前面我们看到,使用
CM会自动把TextBox的Text绑定到ViewModel的UserName属性上,CM在这里做的事情有二:一,它需要根据控件的类型(TextBox)来决定对于控件的哪个属性使用绑定。二,它需要根据控件的名字来做智能匹配。关于第二点,稍后再来介绍,先来看看对于不同类型的控件,CM是如何控制的。
CM使用ElementConvention来做Convention处理的类型描述,CM提供了ConventionManager这个静态类允许操作ElementConvention。ElementConvention如下:
以TextBox为例,其中ElementType是它的类型(TextBox),ParameterProperty是作用的Property(Text),GetBindableProperty是一个Func函数,对于TextBox来说返回TextProperty,CreateTrigger返回触发事件(TextChanged)。
ConventionManager默认提供了一些类型的处理描述,如在ConventionManager的静态函数中:
可以调用ConventionManager的AddElementConvention方法来加入新类型的处理描述,或者更改原有的ElementConvention。
Convention
讲过了Convention的处理描述(ElementConvention),来谈一下具体的Convention过程。
整个Convention过程是从View和ViewModel的Bind开始的:
- 手动或CM自动调用ViewModelBinder的Bind方法(传入对应的View和ViewModel),CM会使用View.GetApplyConventions(View)方法,来确定是否要在该View上使用Convention。如果不希望使用CM的Convention功能,可以在View上设置附加属性View.SetApplyConventions为False或者设置全局标志ViewModelBinder.ApplyConventionsByDefault为False,取消Convention。
- 默认是开启Convention功能的,开始进入View的Convention,遍历View的VisualTree,获得所有有名字的控件。
- 对这些有名字的控件使用Convention,Convention分两类,ActionConvention和PropertyConvention,Action指方法调用,Property指属性绑定。
- 如果使用AddElementConvention时,最后一个参数传入了触发事件Trigger的名字(Click,TextChanged)等,证明该类型控件是可以有ActionConvention的。CM会用控件的名字来匹配ViewModel中所有公共方法的名字,如果存在相同则对它使用ActionConvention。如前面的Button->ChangeName匹配ViewModel对应方法的名字,这个方法被触发的事件是AddElementConvention注册的Click事件。
- 如果使用AddElementConvention时,第二个参数传入了属性名字,则对该类型控件使用PropertyConvention,方法与上面类似,用控件的名字匹配ViewModel中所有公共属性的名字,如果存在则设置绑定。
Validate,StringFormat,Converter…
如果对控件使用了PropertyConvention,相当于CM自动帮我们设置了属性绑定,那么Binding里原有的一些Validate,StringFormat,Converter怎么处理?
ConventionManager提供了SetBinding来做PropertyConvention,来看一下它的代码:
其中的ApplyBindingMode,ApplyValueConverter等等都是Func,允许你替换原有的实现加入你自己的处理,默认的处理都非常简单。也就是说,CM帮我们自动设置的属性绑定是最基本的绑定。CM在PropertyConvention上的处理是很简单的,如果你在属性上已经手动设置了Text={Binding Path=ChangeName},那么CM会忽略这个属性。PropertyConvention只产生最基本的属性绑定,如果你希望让产生的绑定智能化你可以替换这些Func来加入你自己的处理。这也是CM整体的一个设计思路,做一些智能操作简化程序,这些操作可以开启或关闭,只做最基本的事不做多余,预留一些扩展性允许定制。
总结
CM设计的最大成功处在于它的简洁和灵活,它可以很好的和其他一些框架合作,也可以比较方便的插入到原有的代码中。如果你已经有了自己的框架,不妨试着合并一下CM。关于CM其他的一些功能就不做更多介绍了,如有有什么疑问欢迎给我留言,也希望和朋友们多多交流,谢谢。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。