在WPF的XAML里,依赖属性可以使用基于BindingBase之类的MarkupExtensin
读取XAML时,会自动的把该BindingBase转换为BindingExpressionBase
然后再放入DependencyObject的EffectiveValueEntry里
那么问题来了,在我们自己做一个轻量级依赖框架时,为什么读取BindingBase会报错
假设,一个属性名称为Title,类型为string
XAML文档为
class="brush:html;gutter:true;"><Page xmlns="http://schemas.wodsoft.com/web/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{Binding Content, ElementName=source}"> <ContentControl Name="source" Content="Test"/> </Page>
在该轻量级框架里
Binding和WPF的一样
在ProvideValue方法执行时,同样会返回BindingExpression
如果读取该XAML,则会报错
类型“Wodsoft.Web.Data.BindingExpression”的对象无法转换为类型“System.String”。
因为XAML读取器会使用CLR来赋值,即使用Title属性的Setter来赋值
显然,BindingExpression无法给Title直接赋值
那么WPF是如何办到的呢?
也许你用过WPF的XamlReader,位于System.Windows.Markup下
该类的静态方法Load能读取XAML内容
同样也能正确读取Binding等MarkupExtension
该方法核心用到XamlXmlReader与XamlObjectWriter
一个读取XAML内容,一个把XAML内容变成Object
我们现在就要通过这两个类实现我们的需求
首先实现一个ObjectReader
public class ObjectReader { public static object Load(Stream stream) { XamlXmlReader reader = new XamlXmlReader(stream); XamlObjectWriter writer = new ObjectWriter(); while (reader.Read()) { writer.WriteNode(reader); } writer.Close(); return writer.Result; } }
XamlXmlReader就用原本的Reader
它负责读取XAML文档内容
我们要写一个ObjectWriter,继承自XamlObjectWriter
在里面实现我们的依赖系统
public class ObjectWriter : XamlObjectWriter { public ObjectWriter() : base(new XamlSchemaContext()) { } //设置属性值 protected override bool OnSetValue(object eventSender, XamlMember member, object value) { if (eventSender is DependencyObject) { //获取依赖属性 DependencyProperty dp = DependencyProperty.FromName(member.Name, member.DeclaringType.UnderlyingType); if (dp == null) { //如果不是依赖属性,则使用CLR方法赋值 return base.OnSetValue(eventSender, member, value); } DependencyObject target = (DependencyObject)eventSender; //使用自己框架的SetValue方法赋值 target.SetValue(dp, value); return true; } else return base.OnSetValue(eventSender, member, value); } //写入成员方法 public override void WriteStartMember(XamlMember property) { //判断是否是依赖类型 if (property.DeclaringType != null && property.DeclaringType.UnderlyingType.IsSubclassOf(typeof(DependencyObject))) { //如果是属性 if (property.UnderlyingMember is PropertyInfo) { //防止目标类型未调用静态构造函数 //这里我不知道还有什么方法可以引发类型的静态构造函数 if (_Instance == null) _Instance = Activator.CreateInstance(property.DeclaringType.UnderlyingType); //获取依赖属性 DependencyProperty dp = DependencyProperty.FromName(property.Name, property.DeclaringType.UnderlyingType); if (dp != null) { //如果是依赖属性 //覆盖XamlMember //使用我们自己MemberInvoker property = new XamlMember((PropertyInfo)property.UnderlyingMember, SchemaContext, new ObjectMemberInvoker(dp)); } } } base.WriteStartMember(property); } private object _Instance; private bool _IsDependencyObject; }
OnSetValue方法是设置普通值类型的属性时用到的
WriteStartMember则是当非值类型属性时调用到
这里需要编写一个ObjectMemberInvoker,继承自XamlMemberInvoker
我们需要重写GetValue和SetValue方法
这样我们就能达到我们的目标了
public class ObjectMemberInvoker : XamlMemberInvoker { public ObjectMemberInvoker(DependencyProperty property) { Property = property; } public DependencyProperty Property { get; private set; } public override object GetValue(object instance) { DependencyObject d = (DependencyObject)instance; return d.GetValue(Property); } public override void SetValue(object instance, object value) { DependencyObject d = (DependencyObject)instance; if (value is BindingExpression) { //... } else d.SetValue(Property, value); } }
在SetValue方法里判断value
如果是绑定类则调用相关方法
否则调用依赖属性的设置方法
现在我们就能正常读取绑定而不会报错了
结束语
XAML很强大,可以扩展出很多东西
但是里面有很多东西微软是没有开放的
拿来做框架会遇到很多坑
甚至于没有解决方法
更多出现于VS的XAML编辑器里
比如这个问题
http://stackoverflow.com/questions/18671317/each-dictionary-entry-must-have-an-associated-key
这个BUG已经有人报告给VS团队并通过了
但至今未解决……