上一篇文章([UWP]如何使用代码创建DataTemplate(或者ControlTemplate))介绍了在UWP上的情况,这篇文章再稍微介绍在WPF上如何实现。

FrameworkElementFactory用于以编程的方式创建模板,虽然文档中说不推荐,但WPF中常常使用这个类,例如DisplayMemberTemplateSelector

  1. FrameworkElementFactory text = new FrameworkElementFactory(typeof(TextBlock));
  2. Binding binding = new Binding
  3. {
  4. Path = new PropertyPath("Name")
  5. };
  6. text.SetBinding(TextBlock.TextProperty, binding);
  7. var xmlNodeContentTemplate = new DataTemplate();
  8. xmlNodeContentTemplate.VisualTree = text;
  9. xmlNodeContentTemplate.Seal();
  10. ListControl.ItemTemplate = xmlNodeContentTemplate;

使用方式如上,这种方式可以方便地使用代码设置绑定或属性值,并且提供了AppendChild方法用于创建复杂的树结构。但是一旦这样做将使代码变得很复杂,建议还是不要这样做。

和UWP一样,WPF也支持使用XamlReader构建模板,只不过需要将

  1. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

改为

  1. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

和UWP不一样的是WPF还有XamlWriter这个工具。

XamlWriter提供一个静态 Save 方法,该方法可用于以受限的 XAML 序列化方式,将所提供的运行时对象序列化为 XAML 标记。如果使用这个类说不定可以用普通的方式创建一个UI元素并且最终创建它对应的DataTemplate,例如这样:

  1. TextBlock text = new TextBlock();
  2. Binding binding = new Binding("Name");
  3. text.SetBinding(TextBlock.TextProperty, binding);
  4. string xaml = string.Empty;
  5. using (var stream = new MemoryStream())
  6. {
  7. XamlWriter.Save(text, stream);
  8. using (var streamReader = new StreamReader(stream))
  9. {
  10. stream.Seek(0, SeekOrigin.Begin);
  11. xaml = streamReader.ReadToEnd();
  12. }
  13. }
  14. var template = (DataTemplate)XamlReader.Parse(@"
  15. <DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
  16. xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
  17. " + xaml + @"
  18. </DataTemplate>");

但现实没有这么简单,在生成xaml的那步就出错了,声称的xaml如下:

  1. <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以便可以序列化:

  1. internal class BindingConvertor : ExpressionConverter
  2. {
  3. public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
  4. {
  5. if (destinationType == typeof(MarkupExtension))
  6. return true;
  7. else return false;
  8. }
  9. public override object ConvertTo(ITypeDescriptorContext context,
  10. System.Globalization.CultureInfo culture, object value, Type destinationType)
  11. {
  12. if (destinationType == typeof(MarkupExtension))
  13. {
  14. BindingExpression bindingExpression = value as BindingExpression;
  15. if (bindingExpression == null)
  16. throw new Exception();
  17. return bindingExpression.ParentBinding;
  18. }
  19. return base.ConvertTo(context, culture, value, destinationType);
  20. }
  21. }

然后,需要由TypeDescriptor告诉大家要使用这个TypeConverter:

  1. internal static class EditorHelper
  2. {
  3. public static void Register<T, TC>()
  4. {
  5. Attribute[] attr = new Attribute[1];
  6. TypeConverterAttribute vConv = new TypeConverterAttribute(typeof(TC));
  7. attr[0] = vConv;
  8. TypeDescriptor.AddAttributes(typeof(T), attr);
  9. }
  10. }
  11. EditorHelper.Register<BindingExpression, BindingConvertor>();

然后就可以愉快地使用了:

  1. Binding binding = new Binding("Name");
  2. TextBlock text = new TextBlock();
  3. text.SetBinding(TextBlock.TextProperty, binding);
  4. StringBuilder outstr = new StringBuilder();
  5. //this code need for right XML fomating
  6. XmlWriterSettings settings = new XmlWriterSettings
  7. {
  8. Indent = true,
  9. OmitXmlDeclaration = true
  10. };
  11. var dsm = new XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings))
  12. {
  13. //this string need for turning on expression saving mode
  14. XamlWriterMode = XamlWriterMode.Expression
  15. };
  16. XamlWriter.Save(text, dsm);
  17. var xaml = outstr.ToString();
  18. var template = (DataTemplate)XamlReader.Parse(@"
  19. <DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
  20. xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
  21. " + xaml + @"
  22. </DataTemplate>");

这样就可以产生正确的XAML了:

  1. <TextBlock Text="{Binding Path=Name}" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />

不过我没遇到这么复杂的业务需求,所以这个方案我也没实际使用过。从原文的评论来看果然还是有些问题,如ValidationRules不能正确地序列化。总之使用要谨慎。

有关TypeConverter和TypeDescriptor的更多信息可见我的另一篇文章了解TypeConverter。不过回顾了这篇文章后我发觉我更需要的是简化文章的能力,所以以后尽可能还是写简短实用些。

FrameworkElementFactory
XamlWriter
XamlWriter and Bindings Serialization
TypeConverter
TypeDescriptor
了解TypeConverter

版权声明:本文为dino623原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/dino623/p/Create-DataTemplate-Programatically-In-WPF.html