在WPF的XAML裡,依賴屬性可以使用基於BindingBase之類的MarkupExtensin
讀取XAML時,會自動的把該BindingBase轉換為BindingExpressionBase
然後再放入DependencyObject的EffectiveValueEntry裡
那麼問題來了,在我們自己做一個輕量級依賴框架時,為什麼讀取BindingBase會報錯
假設,一個屬性名稱為Title,類型為string
XAML文檔為
<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 void OnBeforeProperties(object value) { _Instance = value; //記錄是否是依賴類型 _IsDependencyObject = typeof(DependencyObject).IsAssignableFrom(_Instance.GetType()); base.OnBeforeProperties(value); } //設置屬性值 //只有值類型才會調用 protected override bool OnSetValue(object eventSender, XamlMember member, object value) { if (_IsDependencyObject) { //獲取依賴屬性 DependencyProperty dp = DependencyProperty.FromName(member.Name, member.DeclaringType.UnderlyingType); if (dp == null) { //如果不是依賴屬性,則使用CLR方法賦值 return base.OnSetValue(eventSender, member, value); } DependencyObject target = (DependencyObject)_Instance; //使用自己框架的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團隊並通過了
但至今未解決……