当前位置:网站首页 / WPF / 正文

2020 WPF 取经之路第4天 - 分解 NeoMarkdigXaml XamlObjectWriter

时间:2020年03月18日 | 作者 : aaronyang | 分类 : WPF | 浏览: 250次 | 评论 0

取经7 字符串的截取,以前都用substring

阅读源码时候,发现个代码

image.png

自己测试

image.png

有谁知道性能区别吗?


两个代码记录

string display = siteObjectStr.Substring(8, 8));

if (display[display.Length - 1] == '\\')

{

    display = display.Substring(0, display.Length - 1);

}

return display;


StringBuilder builder = new StringBuilder( siteObjectStr.Substring( 8, 8));

if (builder[builder.Length - 1] == '\\')

{

    builder = builder.Remove(builder.Length - 1, 1);

}

return builder.ToString();


image.png

做了个测试,发现 还是substring好点

测试代码如下

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace TestTest
{
    class Program
    {

        const int _max = 100;
        static void Main()
        {
            char[] buffer = null;

            int offset = 4;
            int length = 4;
            string text = "123abc杨洋123abc杨洋123abc杨洋123abc杨洋123abc杨洋123abc杨洋123abc杨洋123abc杨洋123abc杨洋123abc杨洋123abc杨洋";
            buffer = text.ToCharArray();
            var s1 = Stopwatch.StartNew();
            for (int i = 0; i < _max; i++)
            {

                text.CopyTo(offset, buffer, 0, length);
                string a = new string(buffer, 0, length);
            }
            s1.Stop();

            var s2 = Stopwatch.StartNew();
            for (int i = 0; i < _max; i++)
            {
                string a = text.Substring(4, 4);
            }

            s2.Stop();
            Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) /
                _max).ToString("0.00"));
            Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) /
                _max).ToString("0.00"));
            Console.Read();
        }

    }
}


取经8 后台创建一个模板

     var template = new ControlTemplate();
                var image = new FrameworkElementFactory(typeof(Image));
                image.SetValue(Image.SourceProperty, new BitmapImage(new Uri(url, UriKind.RelativeOrAbsolute)));
                image.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.ImageStyleKey);
                template.VisualTree = image;

                var btn = new Button()
                {
                    Template = template,
                    Command = Commands.Image,
                    CommandParameter = url
                };



取经9 读取xaml,而xaml中有自己的 特殊的命令 和 样式处理

        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
            {
                using (var reader = new XamlXmlReader(stream, new MyXamlSchemaContext()))
                {
                    if (XamlReader.Load(reader) is FlowDocument document)
                    {
                        Viewer.Document = document;
                    }
                }
            }
        }
         class MyXamlSchemaContext : XamlSchemaContext
        {
            public override bool TryGetCompatibleXamlNamespace(string xamlNamespace, out string compatibleNamespace)
            {
                if (xamlNamespace.Equals("clr-namespace:Markdig.Wpf", StringComparison.Ordinal))
                {
                    compatibleNamespace = $"clr-namespace:Markdig.Wpf;assembly={Assembly.GetAssembly(typeof(Markdig.Wpf.Styles)).FullName}";
                    return true;
                }
                return base.TryGetCompatibleXamlNamespace(xamlNamespace, out compatibleNamespace);
            }
        }

image.png

第三方程序集暴露 没有实现的接口

        public static RoutedCommand Hyperlink { get; } = new RoutedCommand(nameof(Hyperlink), typeof(Commands));

客户端实现下

  <FrameworkElement.CommandBindings>
    <CommandBinding Command="{x:Static markdig:Commands.Hyperlink}" Executed="OpenHyperlink" />
  </FrameworkElement.CommandBindings>


到此为止 基于 Markdig.Wpf这个库 已经了解 套路了,稍微看了wpf那个render,也挺好。是new wpf的document中的一个一些控件,而它的xaml,是拼接xaml字符串,然后转对象 XamlReader的




接下来 分解 NeoMarkdigXaml

这个库

image.png

这个库的思路是 "从XamlMarkdownWriter继承,并重写方法GetDefaultStyle。"就是wpf对象方式比上一篇讲的库快一点


Markdig.wpf实现两个渲染器,一个渲染器用于xaml-output,一个渲染器用于wpf-objects

这个库 github上md文件说明

此实现只有一个渲染器,该渲染器具有xaml-token-stream作为输出。 可以使用框架实现的XamlXmlWriter或XamlObjectWriter编写此流。


Markdig.Wpf-toxaml:109ms

Markdig.Xaml-toxaml:850ms

Markdig.Wpf-towpf:12.579ms

Markdig.Xaml-towpf:6.882ms


如果要创建纯xaml文本输出,Neo库会比较慢。 原因是,我的实现根据wpf-object-schema验证了输出。 这是又一个昂贵的步骤,但是可以确保有效的wpf-xaml。


Neo.Markdig.Xaml中的对象创建应该总是比Markdig.Wpf方法更快,因为Neo.Markdig.Xaml使用内部wpf对象创建以及所有缓存和重播机制。

需要证明:在两种情况下,Neo.Markdig.Xaml都应在大型文档上消耗更少的内存。


还是测试 上篇 项目的那个 C:\\test.md 文件,确实比Markdig.Wpf库快2-3倍速度,AY杨洋自己亲自测试的


这个库的用法

	var content = File.ReadAllText(@"c:\\test.md");
			var doc = MarkdownXaml.ToFlowDocument(content,
				new MarkdownPipelineBuilder()
				.UseXamlSupportedExtensions()
				.Build()
			);
			flowDocumentViewer.Document = doc;

入口应该是 ToFlowDocument方法了


取经10 System.Xaml.XamlObjectWriter 学习探索一个未知类库的用法

image.png

新建一个CusMarkdown类

using Markdig;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Xaml;

namespace TestMarkdig.NM
{
    public  static class CusMarkdown
    {
        public static FlowDocument ToFlowDocument(string markdown, MarkdownPipeline pipeline = null)
        {
            using (var writer = new XamlObjectWriter(System.Windows.Markup.XamlReader.GetWpfSchemaContext()))
            {
                var renderer = new XamlMarkdownWriter(writer);
                pipeline.Setup(renderer);

                var document = Markdown.Parse(markdown, pipeline);
                return (FlowDocument)renderer.Render(document);
            }
        }

        public static MarkdownPipelineBuilder UseXamlSupportedExtensions(this MarkdownPipelineBuilder pipeline)
        {
            if (pipeline == null) throw new ArgumentNullException(nameof(pipeline));
            return pipeline
                .UseEmphasisExtras()
                .UseGridTables()
                .UsePipeTables()
                .UseTaskLists()
                .UseAutoLinks();
        } 
    }

}

下图中和 Markdig.Wpf 思路一样

image.png

通过image.png

需要传入一个IMarkdownRenderer的实现

草稿1

image.png

上篇博客我们继承了MarkdownObjectRenderer<TxtRenderer, TObject>


这次继承RendererBase

image.png

然后

image.png

最后基本代码:

 internal class XamlMarkdownWriter: RendererBase
    {
        private XamlObjectWriter writer;

        public XamlMarkdownWriter(XamlObjectWriter writer)
        {
            this.writer = writer;
        }

        public override object Render(MarkdownObject markdownObject)
        {
            throw new NotImplementedException();
        }
    }

到目前未知,还不知道,先换个频道,因为代码看不下去了

番外:根据字符串获得 一个依赖属性

var descriptor = DependencyPropertyDescriptor.FromName(

    propertyName,

    dependencyObject.GetType(),

    dependencyObject.GetType());


// now you can set property value with

descriptor.SetValue(dependencyObject, value);


// also, you can use the dependency property itself

var property = descriptor.DependencyProperty;

dependencyObject.SetValue(property, value);


番外阅读:WPF中的DependencyProperty存储方式详解


从网上摘抄个代码(代码是有问题的,自己记录下, DependencyProperty.FromName这个方法找不到):

    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;
        }
    }
    public class ObjectWriter : XamlObjectWriter
    {
        public ObjectWriter() : base(new XamlSchemaContext()) { }
        object _Instance; 
        protected override void OnBeforeProperties(object value) 
        { 
            _Instance = value; base.OnBeforeProperties(value); 
        }
        //设置属性值
        protected override bool OnSetValue(object eventSender, XamlMember member, object value)
        {
            if (eventSender is DependencyObject)
            {
                //获取依赖属性
                DependencyProperty dp = DependencyPropertyDescriptor.FromName(member.Name, member.DeclaringType.UnderlyingType);
                if (dp == null)
                {
                    //如果不是依赖属性,则使用CLR方法赋值
                    return base.OnSetValue(eventSender, member, value);
                }
                DependencyObject target = (DependencyObject)eventSender;
                //使用自己框架的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.FromNameKey(property.Name, property.DeclaringType.UnderlyingType);
                    if (dp != null)
                    {
                        //如果是依赖属性
                        //覆盖XamlMember
                        //使用我们自己MemberInvoker
                        property = new XamlMember((PropertyInfo)property.UnderlyingMember, SchemaContext, new ObjectMemberInvoker(dp));
                    }
                }
            }
            base.WriteStartMember(property);
        }

        private bool _IsDependencyObject;
    }
    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);
        }
    }

知识:

XamlXmlReader和XamlObjectWriter

一个读取XAML内容,一个把XAML内容变成Object

(以前很少对System.Xaml.dll类库有了解)

在按钮click事件添加测试:

image.png

GetXamlType 返回一个XamlType 这里有点像反射代码,类似GetType,只不过这里针对Xaml中的

同理GetMember

image.png

测试理解:

image.png

获取一下Button,只是这样没有意义,封装下

加一个Stack缓存下类型

        #region 摸索xaml
        public XamlType WriteStartObject(Type type)
        {
            var xamlType = SchemaContext.GetXamlType(type ?? throw new ArgumentNullException(nameof(type)))
                ?? throw new ArgumentOutOfRangeException(nameof(type), type, "Could not resolve xaml type.");

            return WriteStartObject(xamlType);
        }
        private readonly Stack<XamlType> xamlTypes = new Stack<XamlType>();
        public XamlType WriteStartObject(XamlType xamlType)
        {
            xamlTypes.Push(xamlType);



            return xamlType;
        }

        #endregion

image.png


增加一个 对列表 类型的 元素的准备工作,增加一个Items的 标签头

   public XamlType WriteStartObject(XamlType xamlType)
        {
            xamlTypes.Push(xamlType);

            WritePendingStartItems();




            return xamlType;
        }

        private XamlMember currentContentMember = null;
        private void WritePendingStartItems()
        {
            if (currentContentMember != null)
            {
                WriteStartMember(currentContentMember);
                writer.WriteGetObject();
                writer.WriteStartMember(XamlLanguage.Items);

                currentContentMember = null;
            }
        }
        public void WriteStartMember(XamlMember member)
        {
            writer.WriteStartMember(member ?? throw new ArgumentNullException(nameof(member)));
        }


接下来追加文本内容,在文档流里面是用Run节点的,先缓存起来

   private XamlType runType;
        private XamlMember runTextMember;
        private void Button_Click(object sender, RoutedEventArgs e)
        {

            using (writer = new XamlObjectWriter(System.Windows.Markup.XamlReader.GetWpfSchemaContext()))
            {
                SchemaContext = writer.SchemaContext;

                this.runType = SchemaContext.GetXamlType(typeof(Run)) ?? throw new ArgumentNullException(nameof(Run));
                this.runTextMember = runType.GetMember(nameof(Run.Text)) ?? throw new ArgumentNullException(nameof(Run.Text));

image.png

文本内容有两种,一种 什么text就显示啥,一种有的比如双引号是不是特殊的,要不要变成xaml认识的,针对内容的处理,内容是否空

        private readonly StringBuilder textBuffer = new StringBuilder();
        private bool IsPendingText => textBuffer.Length > 0;
        private bool preserveWhitespace = false; 
        private bool appendWhiteSpace = false;
               private void WritePendingText(bool onStartObject)
        {
            if (IsPendingText)
            {
                WritePendingStartItems();

                if (preserveWhitespace)
                {
                    writer.WriteStartObject(runType);
                    writer.WriteStartMember(runTextMember);
                    writer.WriteValue(textBuffer.ToString());
                    writer.WriteEndMember();
                    writer.WriteEndObject();
                    textBuffer.Length = 0;
                }
                else
                {
                    if (appendWhiteSpace && onStartObject)
                    {
                        textBuffer.Append(' ');
                        appendWhiteSpace = false;
                    }

                    writer.WriteValue(textBuffer.ToString());
                    textBuffer.Length = 0;
                }
            }
        }

取经11 StringBuilder的length=0

image.png

回到正题

image.png

这几个代码好比

<Run

<Run Text="

<Run Text="内容...

<Run Text="内容..."

<Run Text="内容..."/>


下面是否追加空格

一般xaml开头标签后面要加空格 才能下一个属性

 textBuffer.Length = 0; 是清空buffer

WriteValue应该是直接把字符串写入


到目前为止可运行代码,前台就1个button,加一个click事件,别太想复杂了:

     XamlSchemaContext SchemaContext = null;
        XamlWriter writer;
        private XamlType runType;
        private XamlMember runTextMember;
        private void Button_Click(object sender, RoutedEventArgs e)
        {

            using (writer = new XamlObjectWriter(System.Windows.Markup.XamlReader.GetWpfSchemaContext()))
            {
                SchemaContext = writer.SchemaContext;

                this.runType = SchemaContext.GetXamlType(typeof(Run)) ?? throw new ArgumentNullException(nameof(Run));
                this.runTextMember = runType.GetMember(nameof(Run.Text)) ?? throw new ArgumentNullException(nameof(Run.Text));


                WriteStartObject(typeof(Paragraph));

            }

        }

        #region 摸索xaml
        public XamlType WriteStartObject(Type type)
        {
            var xamlType = SchemaContext.GetXamlType(type ?? throw new ArgumentNullException(nameof(type)))
                ?? throw new ArgumentOutOfRangeException(nameof(type), type, "Could not resolve xaml type.");

            return WriteStartObject(xamlType);
        }
        private readonly Stack<XamlType> xamlTypes = new Stack<XamlType>();
        public XamlType WriteStartObject(XamlType xamlType)
        {
            xamlTypes.Push(xamlType);

            WritePendingStartItems();
            WritePendingText(true);
            writer.WriteStartObject(xamlType);


            return xamlType;
        }

        private XamlMember currentContentMember = null;
        private void WritePendingStartItems()
        {
            if (currentContentMember != null)
            {
                WriteStartMember(currentContentMember);
                writer.WriteGetObject();
                writer.WriteStartMember(XamlLanguage.Items);

                currentContentMember = null;
            }
        }
        public void WriteStartMember(XamlMember member)
        {
            writer.WriteStartMember(member ?? throw new ArgumentNullException(nameof(member)));
        }

        private readonly StringBuilder textBuffer = new StringBuilder();
        private bool IsPendingText => textBuffer.Length > 0;
        private bool preserveWhitespace = false; 
        private bool appendWhiteSpace = false;
        private void WritePendingText(bool onStartObject)
        {
            if (IsPendingText)
            {
                WritePendingStartItems();

                if (preserveWhitespace)
                {
                    writer.WriteStartObject(runType);
                    writer.WriteStartMember(runTextMember);
                    writer.WriteValue(textBuffer.ToString());
                    writer.WriteEndMember();
                    writer.WriteEndObject();
                    textBuffer.Length = 0;
                }
                else
                {
                    if (appendWhiteSpace && onStartObject)
                    {
                        textBuffer.Append(' ');
                        appendWhiteSpace = false;
                    }

                    writer.WriteValue(textBuffer.ToString());
                    textBuffer.Length = 0;
                }
            }
        } 
        #endregion

到目前为止,还没什么用,接下来Writeitems


增加第二阶段摸索

#region 第二阶段摸索
        public void WriteItems(bool preserveSpaces = false)
        {
            var member = xamlTypes.Peek().ContentProperty ?? throw new ArgumentNullException(nameof(XamlType.ContentProperty));
            WriteStartItems(member, preserveSpaces);

//填写内容

            WriteEndItems();
        } // proc WriteItems
        private bool firstCharOfBlock = true;
        public void WriteStartItems(XamlMember member, bool preserveSpaces = false)
        {
            if (currentContentMember != null)
                throw new InvalidOperationException();

            preserveWhitespace = preserveSpaces;
            appendWhiteSpace = false;
            firstCharOfBlock = true;
            currentContentMember = member;
        } // proc WriteStartItems
        public object WriteEndObject()
        {
            // write pending text
            WritePendingText(false);

            xamlTypes.Pop();
            writer.WriteEndObject();
            return GetResult();
        } // proc WriteEndObject
        private object GetResult()
        => writer is XamlObjectWriter ow
            ? ow.Result
            : null;
        public void WriteEndItems()
        {
            WritePendingText(false);

            if (currentContentMember == null)
            {
                writer.WriteEndMember();
                writer.WriteEndObject();
                writer.WriteEndMember();
            }
            else
                currentContentMember = null;
        }
        #endregion

测试部分增加

image.png

增加第三个测试

 #region 第三阶段
        internal void WriteText(string text)
        {
            if (preserveWhitespace)
                textBuffer.Append(text);
            else
            {
                var l = text.Length;
                for (var i = 0; i < l; i++)
                    AppendChar(text[i]);
            }
        } // proc WriteText
        private void AppendChar(char c)
        {
            if (Char.IsWhiteSpace(c))
                appendWhiteSpace = true;
            else
            {
                if (appendWhiteSpace)
                {
                    if (!firstCharOfBlock)
                        textBuffer.Append(' ');
                    appendWhiteSpace = false;
                }

                firstCharOfBlock = false;
                textBuffer.Append(c);
            }
        }
        #endregion
        public void WriteItems(string text,bool preserveSpaces = false)
        {
             var member = xamlTypes.Peek().ContentProperty ?? throw new ArgumentNullException(nameof(XamlType.ContentProperty));
            WriteStartItems(member, preserveSpaces);
            WriteText(text);

image.png

修改Click下的测试

 WriteItems("AY 2020");

image.png

此时相当于获取了一个Paragraph的一个实例了。

image.png

效果:

                WriteStartObject(typeof(Paragraph));
                WriteItems("AY 2020");
              var _2=  WriteEndObject();
                if (_2 != null)
                {
                    txt2.Document.Blocks.Add((Paragraph)_2);
                }

image.png

XamlObjectWriter有个Result属性,获得WriteEndObject后,可以获得对象实例




 AYUI       www.ayjs.net      AY         杨洋原创编写,请不要转载谢谢







推荐您阅读更多有关于“”的文章

猜你喜欢

额 本文暂时没人评论 来添加一个吧

发表评论

必填

选填

选填

必填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

  查看权限

合肥科大智能常年招聘.NET,Web前端,有想换工作的私聊我AY唯一QQ:875556003和AY交流

抖音号:wpfui,可以看到我的很多作品效果,私活合作请qq联系我

AYUI8社区版Github地址:前往获取

作者:杨洋(AaronYang简称AY,安徽六安人)目前是个人,还没公司AY唯一QQ:875556003和AY交流

高中学历,2015年1月17日开始,兴趣学习研究WPF,目前工作繁忙,不接任何活

声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费.AY主要靠卖技术服务挣钱

不是从我处购买的ayui7源码,我不提供任何技术服务,如果你举报从哪里买的,我可以帮你转正为我的客户,并送demo

查看捐赠

AYUI7.X MVC教程 更新如下:

第一课 第二课 程序加密教程

vs2015 企业版密钥HM6NR-QXX7C-DFW2Y-8B82K-WTYJV

vs2017 企业版密钥NJVYC-BMHX2-G77MM-4XJMR-6Q8QF

标签列表