今天這篇文章,站長來聊聊我最近基本開發完成的 CodeWF.Markdown。
這是一個基於 C# + Avalonia 12 + Markdig 做的 Markdown 渲染控制項。它最早來自 CodeWF.AvaloniaControls,後來我把 Markdown 相關程式碼單獨拆成了一個倉庫和一組 NuGet 套件:渲染控制項、主題資源、範例程式、測試都圍繞 Markdown 預覽這件事來組織。
它的定位不是「把 Markdown 轉成 HTML 後塞進 WebView」,而是把 Markdown AST 渲染成 Avalonia 控制項樹。這樣做的好處是:主題、字型、選取、複製、圖片預覽、程式碼區塊工具列、增量重新整理這些桌面控制項能力,都可以留在 Avalonia 體系裡處理。
這次文章不只放幾張介面圖,我把範例程式也跑起來重新截了一輪素材,包括靜態截圖和 GIF 動畫。先看一段功能巡覽。

為什麼單獨拆一個 Markdown 控制項
我平時寫文章、整理文件、做工具箱頁面時,經常需要一個本機 Markdown 預覽控制項。WebView 能做,但它會把問題帶到另一套體系裡:HTML/CSS 注入、指令碼安全、平台差異、複製行為、圖片預覽視窗、主題資源同步,都要額外處理。
Avalonia 桌面應用裡更自然的方式,是讓 Markdown 直接變成控制項樹:
- 段落、標題、列表、引用、表格是 Avalonia 控制項。
- 程式碼區塊可以掛複製按鈕、語言標識和醒目提示控制項。
- 圖片可以用 Avalonia 的視窗能力做預覽、縮放、旋轉和另存。
- 主題可以走
ResourceDictionary,而不是再維護一套 CSS。 - 多個
MarkdownViewer可以在同一個視窗裡擁有不同排版密度。
這就是 CodeWF.Markdown 的主要方向:做一個適合 Avalonia 應用直接嵌入的 Markdown Viewer,而不是再包一層瀏覽器。
當前套件線
目前倉庫裡主要有這幾條套件線:
CodeWF.Markdown:完整版MarkdownViewer,包含程式碼醒目提示、圖片/SVG、數學公式、多語言資源、增量渲染等能力。CodeWF.Markdown.Themes:完整版控制項範本和排版主題資源。CodeWF.Markdown.Lite:輕量版 Viewer,直接套件引用只保留 Avalonia 和 Markdig,適合只需要基礎 Markdown 預覽的場景。CodeWF.Markdown.Lite.Themes:輕量版對應主題資源。CodeWF.Markdown.Sample:完整版範例程式。CodeWF.Markdown.Lite.Sample:輕量版範例程式。tests/CodeWF.Markdown.Tests:渲染模型、差異服務和主題資源相關測試。
倉庫位址在這裡:https://github.com/dotnet9/CodeWF.Markdown。
當前截圖對應的倉庫版本是 12.0.3.1。我本機已經執行過:
dotnet build CodeWF.Markdown.slnx
dotnet test CodeWF.Markdown.slnx --no-restore
測試專案當前 13 個測試案例通過。建置時會看到 .NET 預覽版 SDK 的提示,但沒有編譯警告和錯誤。
範例程式:左邊編輯,右邊即時預覽
先看主範例程式。介面分成兩部分:左側是 Markdown 輸入,右側是渲染預覽。

上方工具列有幾個關鍵設定:
- 應用主題:淺色、深色。
- 排版主題:切換 Markdown 內容的強調色、標題邊線、引用背景、表格樣式、程式碼區樣式等。
- 緊湊佈局:把字型、行高和區塊間距收緊,適合資訊密度更高的工具介面。
- 語言:當前範例支援簡體中文、繁體中文、英文、日文。
左側下拉選單載入了多份 Markdown 範例檔案,用來覆蓋基礎元素、排版主題、程式碼與表格、列表引用、圖片連結、增量渲染壓力等場景。
這個範例程式不是為了做一個完整 Markdown 編輯器,而是為了驗證控制項在真實桌面視窗裡的表現:視窗縮放、捲動、主題切換、多語言、編輯區持續變更、預覽區增量重新整理,都要放在一起看。
常見 Markdown 元素
完整版使用 Markdig 解析 Markdown。常見元素基本都按控制項樹渲染:
- 標題、段落、粗體、斜體、刪除線。
- 行內程式碼和程式碼區塊。
- 有序列表、無序列表、巢狀列表。
- 任務列表。
- 引用區塊,以及引用區塊裡的列表、程式碼和表格。
- 表格、連結、分隔線。
- 腳註、數學公式等延伸語法。
- HTML inline 和 HTML block 的 fallback。
這裡有一個取捨:CodeWF.Markdown 不執行 HTML。也就是說,Markdown 裡寫 <section>、<span> 這類內容時,它會作為文字安全顯示出來,而不是像瀏覽器一樣執行 HTML 和樣式。對桌面預覽控制項來說,這個預設行為更穩,尤其適合展示外部輸入或使用者編輯內容。
程式碼區塊:醒目提示、語言標籤和複製按鈕
程式碼區塊是 Markdown 預覽裡很容易被忽略、但實際體驗很關鍵的部分。

當前程式碼區塊會顯示語言標籤和複製按鈕,並使用 TextMateSharp 做語法醒目提示。截圖裡可以看到 JSON、C#、TypeScript、Shell 等程式碼區塊的展示效果。
我沒有把程式碼區塊只做成一個純文字框,因為實際用 Markdown 讀文件時經常需要複製程式碼;另外,不同主題下程式碼區背景、邊框、字型大小和按鈕尺寸也要一起跟著資源變化。
控制項裡還預留了 CodeBlockToolRender 事件,用來讓外部專案在程式碼區塊工具區繼續加自己的按鈕。比如有些應用可能要在程式碼區塊上加「執行」「複製為命令」「傳送到終端機」這類入口,這些都不應該寫死在 MarkdownViewer 內部。
表格和長內容

表格不是簡單地拼字串,而是按行、列、儲存格渲染。表頭背景、邊框、文字顏色都從主題資源讀取。
這個點看起來不起眼,但對中文文件很重要。很多 Markdown 文件裡會有較長的中文說明、英文識別碼、連結、版本號碼和路徑。如果表格不能自然換行,預覽區域就會很容易出現橫向撐爆。

範例裡專門放了對齊表格和複雜儲存格內容,用來觀察:
- 表頭和內容區邊框是否穩定。
- 長文字是否在儲存格內換行。
- 行內程式碼、加粗文字和連結是否能在表格裡正常顯示。
- 主題切換後表格樣式是否立即更新。
圖片、SVG 和預覽視窗
Markdown 裡的圖片也是一個獨立控制項 MarkdownImage,不是簡單地把圖片塞到文件裡就結束。

它支援本機圖片、URL 圖片、Data URI,以及 SVG 圖片。

圖片載入失敗時,會顯示 fallback 文字,不會讓整個 Markdown 文件渲染中斷。

點擊圖片後會開啟預覽視窗:

預覽視窗支援:
- 縮小、放大。
- 1:1 顯示。
- 適應視窗。
- 左旋、右旋。
- 另存為。
SVG 圖片在預覽時會額外轉成點陣圖作為預覽輸入,避免原始 Viewer 清理圖片資源後預覽視窗也跟著失效。最近這輪也專門處理了圖片資源釋放問題:Markdown 被替換、控制項離開可視樹、圖片載入還沒完成時,都要取消未完成任務並釋放點陣圖。
數學公式和化學表達式

完整版裡接入了 Sylinko.CSharpMath.Avalonia,用於數學公式渲染。最近新增的內部 MarkdownMathView,主要是為了讓公式前景色能跟隨當前 Markdown 主題。

這類細節在淺色主題裡不明顯,一切換到深色主題就會暴露出來:普通文字已經變成淺色,但公式如果仍然使用固定黑色,就會看不清。把公式也納入 Markdown 主題體系,才能保證整篇文件的閱讀體驗一致。
範例文件裡還包含化學表達式處理,用於把部分 LaTeX 化學命令轉成更適合複製和純文字提取的內容。它不是要取代專業公式編輯器,而是讓常見技術文章裡的公式段落,在桌面預覽裡能正常顯示。

排版主題:不是只換一個顏色
排版主題是這次控制項裡我比較重視的一塊。它不是簡單改一個 Accent Color,而是一整套 Markdown 閱讀資源。
主題切換效果可以看這張 GIF:

當前主題資源裡包含這些方向:
Basic- 橙心
- 墨黑
- 彩紫
- 嫩青
- 綠意
- 紅緋
- 藍螢
- 科技藍
- 蘭青
- 山吹
- 前端之峰
- 極客黑
- 簡潔
下面是科技藍主題下的效果:

深色應用主題加極客黑排版主題,則更適合程式碼和技術筆記:

主題資源統一使用固定 Key,例如:
<SolidColorBrush x:Key="CodeWFMarkdownAccentBrush" Color="#0F766E" />
<SolidColorBrush x:Key="CodeWFMarkdownQuoteBackgroundBrush" Color="#ECFDF5" />
<x:Double x:Key="CodeWFMarkdownParagraphLineHeight">31</x:Double>
這樣外部專案要自訂主題時,不需要改控制項程式碼,只要覆蓋這些資源 Key 即可。預設主題包也提供了 Light/Dark 兩套資源,切換 Avalonia 應用主題後,同一個 Markdown 排版主題會載入對應的亮色或暗色資源。
單個 Viewer 可以單獨覆蓋主題
一個真實應用裡,可能不只一個 Markdown 預覽區。比如左邊是正文預覽,右邊是評審記錄;或者主文件用普通排版,旁邊摘要用緊湊排版。
所以 MarkdownViewer 現在支援單個控制項覆蓋:
TypographyThemeTypographySize
<md:MarkdownViewer
Markdown="{Binding Markdown}"
TypographyTheme="Simple"
TypographySize="Small" />
多 Viewer 範例就是為了驗證這個場景:

Viewer A 可以跟隨上方統一設定,Viewer B 可以自己使用「簡潔 + 緊湊」。局部設定會寫入當前 Viewer 的資源範圍,不會污染同級 Viewer。
這一點對桌面應用很實用。很多應用不是只有一個「文章閱讀頁」,而是把 Markdown 預覽嵌在設定說明、版本更新、AI 回覆、日誌解釋、文件詳情、比對檢視裡。不同區域的閱讀密度不同,不能只靠全域主題一把梭。
增量渲染:盡量只取代變更區域
如果每次輸入一個字元都完整重建整個 Markdown 文件,短文字沒問題,長文件就容易卡。
CodeWF.Markdown 裡做了一個增量渲染路徑:控制項會保留已經渲染的區塊,文字變化後先透過差異服務判斷變更範圍,再盡量只取代受影響的區塊。
範例程式裡有一個「開始增量演示」按鈕,會自動模擬三類變化:
- 取代文件中一段內容。
- 在正文中部插入中文片段。
- 在文件尾部追加新的 Markdown 區塊。
效果如下:

這套邏輯不是為了炫技,而是為了貼近真實編輯場景。中文文件裡經常不是只改一個英文單詞,而是改一整句話、插入一段說明、追加一個表格或程式碼區塊。增量渲染要處理的是這些連續文字變更。
當然,它也不是不顧正確性地強行局部重新整理。如果變更範圍過大、結構影響太多,或者局部取代不適合繼續復用舊區塊,就會退回完整渲染。對預覽控制項來說,正確性仍然比「永遠局部重新整理」更重要。
選區和複製
MarkdownViewer 是唯讀預覽控制項,但唯讀不代表不能互動。
當前控制項支援:
- 選擇渲染後的文字。
- 複製選區。
- 在空白處複製整篇渲染文字。
- 程式碼區塊單獨複製。
這裡有一個細節:渲染文字不是簡單返回原始 Markdown。比如表格、列表、圖片 alt、公式、任務列表等內容,都需要提取成適合複製的純文字。倉庫裡的 MarkdownParser 和共享渲染模型就承擔了一部分這類工作。
做這個功能時我更關心的是「複製出來能不能用」。如果使用者只是想從 Markdown 預覽裡複製一段說明、一個表格或一個程式碼區塊,就不應該被 Markdown 標記符打擾。
Lite 套件:給基礎預覽留一條輕量路徑
完整版功能比較全,但依賴也會多一些:
TextMateSharp用於程式碼醒目提示。Svg.Controls.Skia.Avalonia和Svg.Skia用於 SVG。Sylinko.CSharpMath.Avalonia用於數學公式。Lang.Avalonia.Json用於多語言資源。
有些專案只需要基礎 Markdown 預覽,不需要這些延伸能力。為這個場景,我又拆了 CodeWF.Markdown.Lite。
Lite 版保留:
- 標題、段落、列表、任務列表。
- 引用、表格。
- 點陣圖圖片。
- 純文字程式碼區塊和複製按鈕。
- 對應的 Lite 主題套件。
它的直接套件引用只保留 Avalonia 和 Markdig,適合對依賴體積比較敏感的專案。如果需要程式碼醒目提示、SVG、數學公式、圖片預覽視窗這些能力,就用完整版。
接入方式
如果使用完整版,安裝套件:
Install-Package CodeWF.Markdown.Themes
如果習慣使用 .NET CLI,也可以這樣加套件:
dotnet add package CodeWF.Markdown.Themes
對應 NuGet 位址:
如果只需要輕量版基礎預覽,則安裝 Lite 套件線:
dotnet add package CodeWF.Markdown.Lite.Themes
Lite 對應 NuGet 位址:
專案原始碼在 GitHub:https://github.com/dotnet9/CodeWF.Markdown。
在 App.axaml 引入主題:
<Application
xmlns="https://github.com/avaloniaui"
xmlns:markdown="https://codewf.com">
<Application.Styles>
<FluentTheme />
<markdown:MarkdownThemes />
</Application.Styles>
</Application>
頁面裡直接使用 MarkdownViewer:
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:md="https://codewf.com">
<ScrollViewer
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<md:MarkdownViewer
Markdown="{Binding Markdown}"
TypographyTheme="Simple"
TypographySize="Small" />
</ScrollViewer>
</UserControl>
如果不指定 TypographyTheme 和 TypographySize,預設是 Basic + Normal,也可以在 MarkdownThemes 上設定全域預設。
執行時切換資源可以呼叫:
MarkdownThemes.OverrideTypographyResources(
app,
MarkdownTypographyThemes.BlueGlow,
MarkdownTypographySizes.Small);
也可以只覆蓋某個視窗或某個控制項範圍,避免影響全域應用樣式。
倉庫組織
當前倉庫結構大致是這樣:
src/
CodeWF.Markdown
CodeWF.Markdown.Themes
CodeWF.Markdown.Lite
CodeWF.Markdown.Lite.Themes
CodeWF.Markdown.Sample
CodeWF.Markdown.Lite.Sample
CodeWF.Markdown.Shared
tests/
CodeWF.Markdown.Tests
CodeWF.Markdown.Shared 裡放共享渲染模型、Markdown 解析、差異服務等程式碼。完整版、Lite 和測試都能複用這一層邏輯。
主題資源放在 CodeWF.Markdown.Themes 和 CodeWF.Markdown.Lite.Themes 裡。這樣控制項程式碼、預設範本、排版資源可以分別維護,外部專案也能選擇只引用自己需要的套件。
最近這輪做了什麼
從更新日誌看,最近幾版主要在補這些東西:
- 新增
CodeWF.Markdown.Lite和對應主題套件、範例應用。 MarkdownViewer新增TypographyTheme與TypographySize,支援單個 Viewer 覆蓋。MarkdownThemes新增緊湊型排版資源。- 範例應用調整為 Tab 結構,新增多 Viewer 演示。
- 多語言資源從 Resx 切換為 JSON,並隨 NuGet content files 散佈。
- 改進圖片資源釋放和圖片預覽視窗資源持有。
- 新增內部
MarkdownMathView,讓數學公式顏色跟隨當前主題。 - 補充 Markdown、主題、SVG 及相關組件的裁剪保留設定,改善裁剪發佈相容性。
這些工作看起來不如「新增一個大功能」顯眼,但對控制項長期可用很重要。尤其是主題資源、圖片釋放、局部 Viewer 覆蓋、Lite 套件線,它們決定了這個控制項能不能被放進真實專案,而不是只能在範例裡好看。
還可以繼續打磨的地方
CodeWF.Markdown 現在已經能覆蓋我自己常用的 Markdown 預覽場景,但它仍然有繼續打磨空間:
- 主題可以繼續增加,並統一更多邊角細節。
- 長文件與複雜表格還可以繼續做壓力測試。
- 程式碼醒目提示語言覆蓋可以繼續驗證。
- 圖片預覽視窗的快速鍵和互動還可以更完整。
- 文件範例可以補更多「如何在業務專案中接入」的片段。
- Lite 和完整版的能力邊界需要在 README 裡寫得更直觀。
- AOT、裁剪發佈和不同平台字型差異還可以繼續測。
我現在對這個控制項的目標比較明確:先把常見閱讀和預覽場景做穩,再逐步補進階能力。Markdown 控制項很容易越做越散,所以套件線、主題資源、共享渲染模型和測試要先立住。
最後
CodeWF.Markdown 是我從自己專案裡拆出來的 Avalonia Markdown 渲染控制項。它的價值不在於「我也能渲染幾個標題和列表」,而在於把 Markdown 預覽作為桌面控制項認真處理:
- 不是 WebView,而是 Avalonia 控制項樹。
- 不是單一樣式,而是排版主題資源。
- 不是只能全域設定,而是支援單個 Viewer 覆蓋。
- 不是每次輸入都完整重建,而是盡量走增量渲染。
- 不是只做完整版,也給基礎預覽留了 Lite 套件線。
對桌面工具、文件管理、AI 回覆預覽、更新日誌展示、設定說明、開發者工具箱這類場景來說,一個可主題化、可複製、可嵌入、可長期維護的 MarkdownViewer 還是很有價值的。
後面站長會繼續把它用到自己的工具和文章工作流裡,一邊用一邊補真實場景裡會遇到的細節。比起做一個一次性的 Demo,我更想把它打磨成 Avalonia 專案裡可以直接拿來用的 Markdown 預覽控制項。