WPF 的 Markdown 渲染方案
本文适合想要了解技术实现细节的读者。如果你想要一个开箱即用的解决方案,可以试试 MdViewer,笔者强烈推荐。本文中并没有介绍所有实现细节,如需更多细节请移步 MdViewer 的仓库查看。
最近笔者参与的项目有在 WPF 应用程序中渲染 Markdown 的需求。网上没找到太系统性的总结,故笔者在这里记录一下研究成果。大致有下面几种思路:
- 基于 Web 渲染:先用 CommonMark 之类的 Markdown 解释器把
.md解释成 HTML,再以网页的形式显示出来。这种方式需要自行编写.css来为 Markdown 设置样式,但正因此灵活性更高,允许控制显示细节。实测这种方式的显示效果较好。 - 基于 Xaml:把 Markdown 解释成
FlowDocument对象,然后直接用 WPF 组件渲染。实测感觉显示的效果很一般。
先说结论
笔者认为 Web 渲染的效果更好:传送门
-
推荐小型程序使用 Markdig + WebBrowser。视觉效果优于基于 Xaml 的解释器(略有缺陷),只需额外的 148 KB 发布体积。强烈建议使用 MdViewer,其采用此方式实现,而且开箱即用,非常方便。
-
如果你不在乎发布体积,用 WebViewer2。这会带来额外的 >10 MB 发布体积,但是会有最好的视觉效果。
下面就介绍一下使用的 NuGet 第三方库以及实现细节。
Markdown 解释器
下面这个网站可以很方便地对比不同的 Markdown 解释器的渲染效果:
以下是一个 Markdown 例子(后文统一使用此例子来测试效果):
# 这是标题
And this is some text with **bold**, *italic* and ~~delete-line~~. 中文效果如何?
Can you see this `inline code block` ?
```
Also this is a code block test.
```
- This is an unordered list.
## ...and this is a h2 title
Is this [LINK](https://www.example.com) avaliable?
| A | B |
|---|---|
|This should be|a table|
下面是这个例子的 HTML 解释,你可以从中看到不同的 Markdown 元素是怎么解释成 HTML 的:
一种可能的解释(HTML)
<h1>这是标题</h1>
<p>
And this is some text with <strong>bold
</strong>, <em>italic
</em> and <del>delete-line
</del>. 中文效果如何?
</p>
<p>
Can you see this <code>inline code block</code> ?
</p>
<pre>
<code>Also this is a code block test.</code>
</pre>
<ul>
<li>This is an unordered list.</li>
</ul>
<h2>...and this is a h2 title</h2>
<p>
Is this <a href="https://www.example.com">LINK</a> avaliable?
</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
</tr>
</thead>
<tbody>
<tr>
<td>This should be</td>
<td>a table</td>
</tr>
</tbody>
</table>
CommonMark.NET
CommonMark 实际上是一套 Markdown 规范,而 CommonMark.NET 则是基于该规范的 .NET 解释器。其支持大部分的 Markdown 语法,除了:
- 表格(如
|---|---|式的管道表) - 删除线(形如
~~something~~的语法)
CommonMark 速度快且轻量级,CommonMark.dll 只有 148 KB。如果不需要渲染表格,笔者非常推荐这个解释器。以下是一个例子:
var result = CommonMark.CommonMarkConverter.Convert("Hello, **world**!");
Markdig
在 NuGet 上搜索“Markdown”,下载数量最多的是 Markdig。Markdig 同样基于 CommonMark 规范。它重用了 CommonMark.NET 的一些代码,并添加了很多扩展语法支持。Markdig.dll 有 397 KB。下面这个例子是最简单的使用方法(不带扩展):
var html = Markdown.ToHtml("Hello, **world**!");
开启扩展的例子:
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions() // 启用大多数扩展
.Build();
var html = Markdown.ToHtml("Hello, **world**!", pipeline);
Markdig.Wpf
Markdig.Wpf 是一个 Markdig 的拓展库,其提供了一个 <MarkdownViewer>,供直接显示 Markdown 文档。这个库是基于 Xaml 的,最终会通过 FlowDocument 在 WPF 中呈现。笔者写这篇笔记时,这个项目于 2024 年 archived 了,慎用。Github 上没有太多的使用指南。有一篇教程可供参考:Markdig.Wpf显示图片、导航栏和链接跳转-CSDN博客。这里简单介绍一下怎么用:
-
首先在
<Windows>中添加命名空间:xmlns:md="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" -
插入标签(建议一定要设置尺寸,下同):
<md:MarkdownViewer Width="800" Height="500" x:Name="MdViewer"/> -
在窗口加载时设置 Markdown:
MdViewer.Markdown = "Hello, **world**!";
效果如下,感觉很一般:

Markdig.Wpf
Neo.Markdig.Xaml
Neo.Markdig.Xaml 是一个 Markdig 的拓展库,提供了一个把 Markdown 文档转换成 FlowDocument 的方法。与用法如下:
-
插入标签:
<FlowDocumentScrollViewer Width="800" Height="500" x:Name="FlowDocumentViewer"/> -
在窗口加载时加载 Markdown:
var markdown = "Hello, **world**!";
FlowDocumentViewer.Document = MarkdownXaml.ToFlowDocument(markdown,
new MarkdownPipelineBuilder()
.UseXamlSupportedExtensions()
.Build()
);
笔者感觉 Neo.Markdig.Xaml 的视觉效果和 Markdig.Wpf 非常相似,因而这里就不放效果了。
Web 渲染
把 Markdown 解释成 HTML 后,我们便可以在 WPF 应用程序中以网页形式显示出来。(推荐使用前文提到的 Markdig 并开启拓展)
MdViewer
笔者编写了一个 NuGet 包:MdViewer,利用 Markdig 解释 Markdown,组装成网页,最后用 WebBrower 展示。笔者强烈推荐使用 MdViewer,这样不需要手动造轮子。具体可以看项目的 README,此处不再赘述。
下面的内容仅供展示核心原理,实现并不完备。完整的实现请参见 MdViewer。
WebBrowser
WPF 内置了 WebBrowser 以提供网页显示功能。优点是启动快、无需额外的 .dll。不过其用的是很旧的 IE 内核,CSS 支持很有限。如果不是非常追求美观(以及复杂功能),完全可以使用 WebBrowser。比较明显的缺陷是没有抗锯齿和无序列表不美观。
-
准备一个 CSS,用于设置网页样式。具体来说可以做成嵌入的资源,运行时通过反射获取资源。(此处略)
-
把 CSS 和解释后的 Markdown 组装:
var html = $@"<html>
<head><meta charset=""UTF-8""><style>{css}</style></head>
<body>{content}</body>
</html>"; // content 就是 Markdown 解析成的 HTML这里不写
<!doctype html>也是可以的。推荐不写,这样可以和上面的 CSS 配合的很好。 -
插入标签:
<WebBrowser Width="800" Height="500" x:Name="WebViewer"/> -
在窗口加载时导航到 HTML:
WebViewer.NavigateToString(html);
// 点击链接时,用默认浏览器打开
WebViewer.Navigating += (s, e) => {
e.Cancel = true;
System.Diagnostics.Process.Start("explorer.exe", e.Uri.ToString());
};
效果如下,除了之前说的两点缺陷以外看着都很舒服:
Markdig(开启拓展)+ WebBrowser
在 WebBrowser 上右键可以查看页面的 HTML 代码。
WebView2
WebView2 是微软官方推出的一个 WPF 组件,以 Edge 为内核实现网页功能,因而比内置的 WebBrowser 更强大。缺点是启动较慢(感觉有 500ms),而且需要附加许多文件(见后面描述),而且需要目标计算机上安装 Edge 或 WebView2 runtime(不过 Windows 10+ 好像都内置了 Edge)。使用过程如下(组装过程省略):
-
首先在
<Windows>中添加命名空间:xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf" -
插入标签:
<wv2:WebView2 Width="800" Height="500" x:Name="WebViewer"/> -
等待 WebView2 内核加载完成,然后导航到 HTML。我们创建一个异步方法,并在窗口加载时调用:
private async void LoadHtml(string html) {
await WebViewer.EnsureCoreWebView2Async();
WebViewer.NavigateToString(html);
}
效果如下,笔者认为是视觉上最好的:
Markdig(开启拓展)+ WebViewer2
在 WebViewer2 上右键可以使用 Edge 配套的 Dev Tools。这个页面的显示似乎和 Edge 的设置是有关的。比如笔者的 Edge 默认字体是 Noto Sans,此处就是用的 Noto Sans 渲染。
生成目录下还会产生下面的东西:
-
目录
xxx.exe.WebView2/,约 9.38 MB。 -
若干
Microsoft.Web.WebView2.xxx.dll、Microsoft.Web.WebView2.xxx.xml,共 1.46 MB。
总过超过 10 MB,这会使得发布包变大。