在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團隊並通過了
但至今未解決……