用C++和Qt构建实时预览的Markdown编辑器
用 C 写一个带实时预览的 Markdown 编辑器听起来像是一个不小的工作量其实拆开看只有三件事左侧的文本编辑区、右侧的预览区以及把 Markdown 内容即时转换为 HTML 的解析层。这篇文章用 Qt 和 cmark 库从零搭出一个最小可运行版本并逐步把语法高亮、本地图片加载、常见环境问题排查等内容补上。如果你已经能写完一个简单的 Qt Widgets 窗口程序但对 Markdown 解析和实时刷新还没有完整思路这篇文章会给你一条清晰的落地路径。实时预览不是指用户点一下按钮才渲染而是编辑区内容发生变化后预览区自动更新。要做到这一点依赖一个稳定的信号循环和解析库。读完这篇内容后你可以得到一个带实时预览的桌面 Markdown 编辑器骨架如果要继续扩展成笔记工具、技术写作工具或内部文档客户端也可以在此基础上直接加文件管理、主题切换、导入导出等功能。1. 实时预览编辑器要拆成哪几个模块1.1 输入、解析、渲染三层Markdown 编辑器的核心并不神秘。用户在一个文本控件里输入 Markdown 原文程序把原文交给解析器解析器生成 HTML再把 HTML 交给预览控件渲染。整个流程就是“输入 - 解析 - 渲染”三个环节。输入层QPlainTextEdit负责接收键盘输入、支持基本的文本选择、撤销重做。解析层cmark把 Markdown 文本转换成 HTML 字符串。渲染层QTextBrowser把 HTML 字符串展示为富文本外部链接可以交给系统浏览器处理。把这三个环节拆开之后实时预览就只是一个信号连接问题当编辑区的文本发生变化时重新执行一次“解析 - 渲染”即可。这段链路里最容易被低估的是解析层。很多人会先写一个 replace 函数把#替换成h1把**替换成strong。这种方式处理简单的单行语法没问题一旦遇到嵌套列表、代码块、转义字符和链接就会漏洞百出。所以直接使用成熟的 Markdown 解析库是更值得投入的选择。1.2 为什么选 Qt cmark不自己写解析器Markdown 语法看起来简单真正解析起来非常容易出错。列表嵌套、段落换行、行内代码、转义字符、引用块、任务列表、表格每一类都有边界情况。自己写一个支持常用语法的解析器至少要几百行而且处理不完整会造成预览和源码不一致。直接使用开源解析库是更稳妥的方案。cmark 是 CommonMark 官方参考实现用 C 语言编写提供了非常简洁的 C API。它没有额外依赖编译出来只有一个静态库或动态库很适合嵌入到 C 工程里。Qt 则负责 UI、事件循环、富文本显示和跨平台编译。这两个库搭配可以比较轻松地完成一个可用的桌面编辑器。在选型时还要考虑渲染层。最简单的是QTextBrowser它基于QTextDocument支持 HTML 子集不需要额外安装浏览器内核。若需要完整支持 CSS、JavaScript、代码高亮可以使用QWebEngineView但代价是运行时依赖 WebEngine。对于大多数笔记场景QTextBrowser已经足够。注意预览控件的选型会影响发布包体积和启动速度。QTextBrowser 轻量QWebEngineView 渲染能力强两者不是替代关系而是不同场景的取舍。1.3 最小闭环目标和学习环境差异这篇文章的最小目标很明确打开程序后左侧输入 Markdown右侧实时刷新 HTML 渲染结果。先把这条链路跑通再谈高亮、文件保存、主题切换等功能。在学习环境里建议直接使用 Qt Creator 或 CMake 命令行工程不要一开始就加入复杂的插件系统。生产环境则需要考虑打包、日志、配置外置、崩溃收集等问题。如果你是在公司项目里落地还需要把 Markdown 解析结果做缓存、控制在每次按键时是否全量解析以及兼容不同电脑上的运行时版本。2. 环境准备与依赖配置2.1 编译工具链和 Qt 版本选择文章示例使用 Qt Widgets 模块不依赖 QML。推荐 Qt 5.15 或 Qt 6.5 以上二者 API 在本文用到的范围内基本一致。如果你在 Windows 上使用 MSVC需要同时安装 Visual C Redistributable在 Linux 上使用 GCC则需要安装 base-devel 或 build-essential。很多 C 项目启动时报缺少VCRUNTIME140.dll本质上就是 Visual C Redistributable 版本没有装全。如果你是第一次创建工程建议用 Qt Creator 自带的 “Qt Widgets Application” 模板再手动加入 cmark 依赖。本文的代码结构如下MarkdownEditor/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ ├── MainWindow.h │ ├── MainWindow.cpp │ ├── MarkdownHighlighter.h │ └── MarkdownHighlighter.cpp这个结构足够支撑一个带预览、高亮和文件处理的小型桌面程序。如果你后续要加入测试或插件可以在src旁边继续增加tests、plugins目录。2.2 安装 cmark 的三种方式cmark 的安装方式取决于操作系统和包管理器。在 Ubuntu / Debian 上sudo apt update sudo apt install libcmark-dev在 Windows 上使用 vcpkgvcpkg install cmark如果你不想使用系统包管理器也可以直接源码编译git clone https://github.com/commonmark/cmark.git cd cmark mkdir build cd build cmake .. cmake --build . --config Release sudo cmake --install .源码编译方式是通用兜底方案。需要注意的是不同发行版提供的 cmark 版本可能不同CMake config 文件的 target 名称也可能不同。落地上机前先确认安装版本和 CMake 能找到的 target 名称。2.3 CMake 工程里同时连接 Qt 和 cmark在 CMakeLists.txt 中先查找 Qt再查找 cmark。下面是一个兼容 Qt5 / Qt6 的示例cmake_minimum_required(VERSION 3.16) project(MarkdownEditor LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) find_package(Qt6 COMPONENTS Widgets QUIET) if(NOT Qt6_FOUND) find_package(Qt5 5.15 REQUIRED COMPONENTS Widgets) endif() find_package(cmark CONFIG QUIET) if(NOT cmark_FOUND) find_package(PkgConfig REQUIRED) pkg_check_modules(CMARK IMPORTED_TARGET REQUIRED libcmark) endif() if(TARGET cmark::cmark) set(CMARK_TARGET cmark::cmark) elseif(TARGET PkgConfig::CMARK) set(CMARK_TARGET PkgConfig::CMARK) else() set(CMARK_TARGET cmark) endif() add_executable(markdown_editor src/main.cpp src/MainWindow.cpp src/MainWindow.h src/MarkdownHighlighter.cpp src/MarkdownHighlighter.h ) target_link_libraries(markdown_editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${CMARK_TARGET} )关键点有两处。一是CMAKE_AUTOMOC必须开启因为MainWindow和MarkdownHighlighter都是 QObject 子类需要处理信号槽。二是LANGUAGES CXX C要同时包含 C因为 cmark 本身是 C 项目虽然最终通过库连接但某些构建方式会需要 C 语言编译规则。如果find_package找不到 cmark通常是因为没有安装开发包或没有设置CMAKE_PREFIX_PATH。不要直接手写target_link_libraries指向一个假设的路径优先使用 CMake 的 package 机制这样换机器后更容易复现。3. 实现一个最小可运行版本3.1 创建主窗口与左右分栏布局主窗口使用QSplitter把编辑区和预览区左右排布。QSplitter不仅提供分隔条还允许用户拖动调整左右宽度这对 Markdown 编辑器是基本体验要求。#include QMainWindow #include QSplitter #include QPlainTextEdit #include QTextBrowser class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent nullptr); private slots: void renderMarkdown(); private: QPlainTextEdit *editor_; QTextBrowser *preview_; };构造函数的实现如下#include MainWindow.h MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), editor_(new QPlainTextEdit(this)), preview_(new QTextBrowser(this)) { auto *splitter new QSplitter(Qt::Horizontal, this); splitter-addWidget(editor_); splitter-addWidget(preview_); splitter-setStretchFactor(0, 1); splitter-setStretchFactor(1, 1); setCentralWidget(splitter); setWindowTitle(C Markdown Live Preview); resize(1000, 700); connect(editor_, QPlainTextEdit::textChanged, this, MainWindow::renderMarkdown); }在renderMarkdown()尚未实现之前程序可以编译运行但右侧不会更新。先把界面搭出来是为了验证布局是否正常再继续加入解析逻辑。3.2 用 cmark 把 Markdown 转成 HTML这是整篇文章最核心的一段代码。先写一个工具函数输入QString输出QString#include cmark.h QString markdownToHtml(const QString markdown) { QByteArray utf8 markdown.toUtf8(); char *html cmark_markdown_to_html( utf8.constData(), utf8.size(), CMARK_OPT_DEFAULT ); QString result QString::fromUtf8(html); free(html); return result; }cmark_markdown_to_html接受const char*和字节长度所以必须先把 QString 转成 UTF-8 字节数组。返回的char*是 cmark 内部通过 malloc 分配的内存用完后必须调用free()否则每次预览都会泄漏一块内存。CMARK_OPT_DEFAULT表示使用 CommonMark 默认行为。常用选项如下选项作用使用建议CMARK_OPT_DEFAULT默认解析行为不输出源码位置保留 HTML 块中的潜在危险标签基础场景使用CMARK_OPT_SOURCEPOS在输出 HTML 中加入 sourcepos 属性调试解析问题CMARK_OPT_HARDBREAKS将普通换行渲染为br需要保留换行时使用CMARK_OPT_UNSAFE允许渲染原始 HTML 和危险链接仅信任输入时使用CMARK_OPT_SMART将直引号、省略号等转为弯引号文档排版美化场景默认情况下script等原始 HTML 不会被渲染成可执行内容这是安全设计不是 bug。若编辑器用于公开内容不要随便开启CMARK_OPT_UNSAFE。3.3 用 textChanged 信号驱动实时预览QPlainTextEdit::textChanged在每次文本内容变化时触发。在这里连接renderMarkdown就能实现“敲一个字预览区刷新”的效果。void MainWindow::renderMarkdown() { QString html markdownToHtml(editor_-toPlainText()); preview_-setHtml(html); }这段代码简单直接但每次按键都会对全文重新解析。对于几 KB 的文本没有问题一旦打开几百 KB 的文档连续打字会造成明显卡顿。更稳妥的做法是加一个去抖定时器把连续触发合并为一次解析。auto *deb

相关新闻

AI时代小团队设计与开发协作:从设计稿到代码的流程重构

AI时代小团队设计与开发协作:从设计稿到代码的流程重构

上周和一个四人创业团队聊天,他们说现在最痛苦的不是写代码,也不是画图,而是设计师交付完 Figma 后,开发者用 AI 生成了一版界面,结果两边都觉得对方没把自己的东西当回事。设计师觉得开发没有还原视觉细节&#xff0c…

2026/11/11 12:39:03 阅读更多 →
ODrive固件v0.3.6 Keil工程移植与编译实操指南

ODrive固件v0.3.6 Keil工程移植与编译实操指南

简介:本资源是ODrive开源电机驱动固件v0.3.6在Keil MDK平台上的完整移植工程,面向嵌入式开发者、机器人控制工程师及高校机电/自动化方向研究者,解决FOC(磁场定向控制)驱动代码在ARM Cortex-M4平台(如STM32…

2026/11/8 21:05:46 阅读更多 →
embabel-agent实战:本地化数据标注与批量处理Agent指南

embabel-agent实战:本地化数据标注与批量处理Agent指南

这次我们来看一个围绕embabel / embabel-agent展开的项目。从命名上拆解,embabel可以理解为 “Enable Label” 的缩写,重心在“标注”;agent则表示它并不是一个单纯的规则脚本,而是带有智能调度、自动执行和批量处理能力的代理式工…

2026/11/1 11:52:18 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/12 5:00:00 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/12 5:13:20 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/12 5:13:20 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/11 17:16:37 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/11 17:16:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/12 5:13:20 阅读更多 →

日新闻

周新闻

月新闻