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

[WPF]如何使用代码创建DataTemplate(或者ControlTemplate)的更多相关文章

  1. [UWP]如何使用代码创建DataTemplate(或者ControlTemplate)

    1. 前言 在UWP中DataTemplate是一个十分重要的功能,并且几乎无处不在,例如DataGrid中的DataGridTemplateColumn: <controls:DataGrid ...

  2. wpf 中用 C# 代码创建 PropertyPath ,以对间接目标进行 Storyboard 动画.

    如图,一个 Rectangle 一个 Button ,点击按钮时要通过动画完成对 Rectangle填充色的渐变动画. Xaml: 1 <Window 2 x:Class="WpfAp ...

  3. WPF在代码中创建DataTemplate时候的异常

    今天写段程序用到了在代码中手动创建DataTemplate, var factory = new FrameworkElementFactory(typeof(OperationColumn));   ...

  4. WPF Template模版之DataTemplate与ControlTemplate【一】

    WPF Template模版之DataTemplate与ControlTemplate[一] 标签: Wpf模版 2015-04-19 11:52 510人阅读 评论(0) 收藏 举报  分类: -- ...

  5. WPF -- DataTemplate与ControlTemplate结合使用

    如深入浅出WPF中的描述,DataTemplate为数据的外衣,ControlTemplate为控件的外衣.ControlTemplate控制控件的样式,DataTemplate控制数据显示的样式,D ...

  6. WPF Template模版之DataTemplate与ControlTemplate的关系和应用【二】

    1. DataTemplate和ControlTemplate的关系 学习过DataTemplate和ControlTemplate,你应该已经体会到,控件只是数据的行为和载体,是个抽象的概念,至于它 ...

  7. 【转】WPF Template模版之DataTemplate与ControlTemplate的关系和应用(二)

    1. DataTemplate和ControlTemplate的关系 学习过DataTemplate和ControlTemplate,你应该已经体会到,控件只是数据的行为和载体,是个抽象的概念,至于它 ...

  8. WPF使用后台C#代码创建Grid

    笔者刚刚接触WPF,菜鸟一枚,在做一个练手程序时遇到这样一个需求,创建一个新的Grid并将其添加至一个ListView中,要求Grid及其子元素应按一定顺序给Name属性赋值,直接使用XAML创建的话 ...

  9. WPF DataTemplate與ControlTemplate

    一. 前言     什麼是DataTemplate? 什麼是ControlTemplate? 在stackoverflow有句簡短的解釋 "A DataTemplate, therefore ...

随机推荐

  1. Spring MVC 实现文件的上传和下载 (八)

    完整的项目案例: springmvc.zip 目录 SpringMVC 中,文件的上传,是通过 MultipartResolver 实现的. 所以,如果要实现文件的上传,只要在 spring-mvc. ...

  2. 自动化测试基础篇--Selenium cookie操作

    摘自https://www.cnblogs.com/sanzangTst/p/8376471.html 在验证浏览器中cookie是否正确时,有时基于真实cookie的测试是无法通过白盒和集成测试进行 ...

  3. Unknown initial character set index '255' received from server. Initial client character set can be

    mysql的连接错误,在网上查找到是编码不匹配的原因, 直接在连接的URL后加上?useUnicode=true&characterEncoding=utf8就可以了

  4. mysql 数据库 命令行的操作——对库的操作

    1.查看所有数据库 show databaese; 2.查看当前所用的数据库 show databases(): 3.切换数据库 use(数据库名): 4.创建数据库 create database ...

  5. python编写文件统计脚本

    python编写文件统计脚本 思路:用os模块中的一些函数(os.listdir().os.path.isdir().os.path.join().os.path.abspath()等) 实现功能:显 ...

  6. Session变量在PHP中的使用

    PHP session 变量用于存储有关用户会话的信息,或更改用户会话的设置.Session 变量保存的信息是单一用户的,并且可供应用程序中的所有页面使用. PHP Session 变量 当您运行一个 ...

  7. js获取请求地址后面带的参数

    浏览器输入页面地址的时候在后面带有请求参数, 页面加载后需要获取携带的参数, 可以使用js, 在页面加载js的时候获取参数 http://localhost:8080/demo/index.html? ...

  8. kafka-connect-hdfs重启,进去RECOVERY状态,从hadoop hdfs拿租约,很正常,但是也太久了吧

    虽说这个算是正常现象,等的时间也太久了吧.分钟级了.这个RECOVERY里面的WAL有点多余.有这么久的时间,早从新读取kafka写入hdfs了.纯属个人见解. @SuppressWarnings(& ...

  9. C#事件の事件访问器

    在真实的项目中,有的对象有相当多的事件,例如一个窗体就有好多种事件.默认情况下,当声明事件时,编译器将内存分配给一个事件字段,一存储事件信息.如果类中有好多事件未使用,则他们会不必要的占用内存. 这种 ...

  10. ElasticSearch(二):允许外网连接服务配置

    上一篇文章的配置,只能在本机使用,但是要想为集群或者其他的机器连接,则需要做以下配置: 一.修改/opt/elasticsearch-6.4.0/config/elasticsearch.yml文件 ...