• [WPF]如何使用代码创建DataTemplate(或者ControlTemplate)


    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

  • 相关阅读:
    【原创】go语言学习(五)函数详解1
    【原创】go语言学习(四)流程控制
    aws使用之负载均衡elb要点
    【转】只因写了一段爬虫,公司200多人被抓!
    【原创】go语言学习(三)字符串串、时间和日期类型
    【原创】go语言之打印目录
    【转】Sentry 入门实战
    【原创】go语言学习(二)数据类型、变量量、常量量
    【原创】导出aws ec2为csv
    (转)实验文档5:企业级kubernetes容器云自动化运维平台
  • 原文地址:https://www.cnblogs.com/dino623/p/Create-DataTemplate-Programatically-In-WPF.html
Copyright © 2020-2023  润新知