1. 前言 上一篇文章( "[UWP]如何使用代碼創建DataTemplate(或者ControlTemplate)" )介紹了在UWP上的情況,這篇文章再稍微介紹在WPF上如何實現。 2. 使用FrameworkElementFactory "FrameworkElementFactory" 用於 ...
1. 前言
上一篇文章([UWP]如何使用代碼創建DataTemplate(或者ControlTemplate))介紹了在UWP上的情況,這篇文章再稍微介紹在WPF上如何實現。
2. 使用FrameworkElementFactory
FrameworkElementFactory用於以編程的方式創建模板,雖然文檔中說不推薦,但WPF中常常使用這個類,例如DisplayMemberTemplateSelector。
FrameworkElementFactory text = new FrameworkElementFactory(typeof(TextBlock));
Binding binding = new Binding
{
Path = new PropertyPath("Name")
};
text.SetBinding(TextBlock.TextProperty, binding);
var xmlNodeContentTemplate = new DataTemplate();
xmlNodeContentTemplate.VisualTree = text;
xmlNodeContentTemplate.Seal();
ListControl.ItemTemplate = xmlNodeContentTemplate;
使用方式如上,這種方式可以方便地使用代碼設置綁定或屬性值,並且提供了AppendChild方法用於創建複雜的樹結構。但是一旦這樣做將使代碼變得很複雜,建議還是不要這樣做。
3. 使用XamlReader和XamlWriter
和UWP一樣,WPF也支持使用XamlReader構建模板,只不過需要將
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
改為
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
和UWP不一樣的是WPF還有XamlWriter這個工具。
XamlWriter提供一個靜態 Save 方法,該方法可用於以受限的 XAML 序列化方式,將所提供的運行時對象序列化為 XAML 標記。如果使用這個類說不定可以用普通的方式創建一個UI元素並且最終創建它對應的DataTemplate,例如這樣:
TextBlock text = new TextBlock();
Binding binding = new Binding("Name");
text.SetBinding(TextBlock.TextProperty, binding);
string xaml = string.Empty;
using (var stream = new MemoryStream())
{
XamlWriter.Save(text, stream);
using (var streamReader = new StreamReader(stream))
{
stream.Seek(0, SeekOrigin.Begin);
xaml = streamReader.ReadToEnd();
}
}
var template = (DataTemplate)XamlReader.Parse(@"
<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
" + xaml + @"
</DataTemplate>");
但現實沒有這麼簡單,在生成xaml的那步就出錯了,聲稱的xaml如下:
<TextBlock Text="" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
可以看到這段XAML並沒有反映text.SetBinding(TextBlock.TextProperty, binding);
這段設置的綁定。具體原因可見XamlWriter.Save 的序列化限制。
值得慶幸的是WPF有足夠長的歷史,在這段歷史里經過了無數人上上下下的折騰,上面提到的問題在10年前已經有人給出瞭解決方案:XamlWriter and Bindings Serialization。
首先,MarkupExtension及其派生類(如Binding)需要有一個TypeConverter以便可以序列化:
internal class BindingConvertor : ExpressionConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(MarkupExtension))
return true;
else return false;
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(MarkupExtension))
{
BindingExpression bindingExpression = value as BindingExpression;
if (bindingExpression == null)
throw new Exception();
return bindingExpression.ParentBinding;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
然後,需要由TypeDescriptor告訴大家要使用這個TypeConverter:
internal static class EditorHelper
{
public static void Register<T, TC>()
{
Attribute[] attr = new Attribute[1];
TypeConverterAttribute vConv = new TypeConverterAttribute(typeof(TC));
attr[0] = vConv;
TypeDescriptor.AddAttributes(typeof(T), attr);
}
}
EditorHelper.Register<BindingExpression, BindingConvertor>();
然後就可以愉快地使用了:
Binding binding = new Binding("Name");
TextBlock text = new TextBlock();
text.SetBinding(TextBlock.TextProperty, binding);
StringBuilder outstr = new StringBuilder();
//this code need for right XML fomating
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = true
};
var dsm = new XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings))
{
//this string need for turning on expression saving mode
XamlWriterMode = XamlWriterMode.Expression
};
XamlWriter.Save(text, dsm);
var xaml = outstr.ToString();
var template = (DataTemplate)XamlReader.Parse(@"
<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
" + xaml + @"
</DataTemplate>");
這樣就可以產生正確的XAML了:
<TextBlock Text="{Binding Path=Name}" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
不過我沒遇到這麼複雜的業務需求,所以這個方案我也沒實際使用過。從原文的評論來看果然還是有些問題,如ValidationRules不能正確地序列化。總之使用要謹慎。
4. 結語
有關TypeConverter和TypeDescriptor的更多信息可見我的另一篇文章瞭解TypeConverter。不過回顧了這篇文章後我發覺我更需要的是簡化文章的能力,所以以後儘可能還是寫簡短實用些。
5. 參考
FrameworkElementFactory
XamlWriter
XamlWriter and Bindings Serialization
TypeConverter
TypeDescriptor
瞭解TypeConverter