1. QT 文本编辑器富文本格式模块到底难在哪
如果你正在用 QT 写一个文本编辑器,做到第四弹这个阶段,大概率已经能打开文件、保存文件、做基本的复制粘贴了。但一旦要往「像 Word 那样选中一段字就能加粗、改颜色、换字体」的方向走,很多人会卡住。核心检索词就是 QT 文本编辑器富文本格式设置,它要解决的问题是:让字体族、字号、字体颜色、加粗、倾斜、下划线、背景色这七种样式,都能作用在光标选区上,并且点击一次生效、再点一次取消,互不干扰。
我见过太多半成品编辑器,加粗按钮点下去没反应,或者点了加粗之后整篇文档都变粗了,又或者倾斜和加粗互相覆盖。根子在于没搞清楚 QTextCursor 和 QTextCharFormat 的配合方式。QTextCursor 代表你当前选中的那块区域,QTextCharFormat 代表你要往这块区域上「盖」的格式属性。关键函数是mergeCharFormat,它只合并你设置的属性,不动其他属性,这样加粗和倾斜才能叠加而不是互相冲掉。
这一篇要交付的是可以直接复制进项目的工具栏动作绑定代码、格式合并逻辑、光标选区处理,以及一份逐项点击验证清单。适合已经有一个能跑的 QT 文本编辑器骨架、想补齐富文本格式菜单的开发者。下面所有代码基于 QT 5/6 的 QMainWindow + QTextEdit 结构,头文件和槽函数命名沿用你项目里已有的风格即可。
在动手之前,先把整体思路理清楚。工具栏上每个按钮对应一个槽函数,槽函数里构造一个 QTextCharFormat,设置对应的属性,然后交给 mergeFormat 去合并到选区。加粗、倾斜、下划线这三个是「开关型」的,需要一个全局变量记录上一次的状态,点一次翻转一次。字体、字号、颜色、背景色是「取值型」的,弹对话框让用户选,选完直接应用。背景色这里有个坑,很多人以为改的是文字背景,其实 QTextEdit 的调色板 Base 改的是整个编辑区背景,真正给文字加背景要用setBackground。这个区别后面会专门讲。
2. TaoToken 统一 Key 通道:给编辑器接入 AI 能力的前置准备
写到这里你可能会问,一个 QT 文本编辑器教程,为什么要提 TaoToken。原因是这样:当你的编辑器富文本格式模块跑通之后,下一步很自然的需求就是加一个「AI 润色」「AI 改写选中段落」的功能。这时候你需要一个稳定的模型调用通道,而不是每个模型都去单独申请 Key、单独配环境。TaoToken 就是干这个的,它把多个模型的调用统一到一个 Key 上,你只需要在代码里改 Base URL 和 Model ID 就能切换模型。
TaoToken 是什么:一个统一的大模型 API 接入通道,兼容 OpenAI 风格的接口格式。能做什么:你用同一个 API Key,就能调用不同厂商的对话模型,适合在编辑器里做文本润色、续写、翻译这类功能。适合谁:正在做桌面端工具、想把 AI 能力嵌进 QT 应用的开发者,尤其是懒得维护多套鉴权逻辑的人。
接入前你需要准备三样东西,我把它叫做「三件套」:Base URL、API Key、Model ID。Base URL 填https://taotoken.net/api,注意这里不加任何多余路径。API Key 去控制台生成,地址是 https://taotoken.net/api-keys 。Model ID 根据你要用的模型填,比如做文本润色可以选一个擅长中文的对话模型。这三样在 QT 里怎么用?你可以用 QNetworkAccessManager 发 POST 请求,请求头带上Authorization: Bearer <你的Key>,请求体里写 model 和 messages。
如果你打算长期在编辑器里做编码辅助或者 Agent 类的功能,可以看一下 Coding Plan,地址是 https://taotoken.net/coding-plan 。它适合那种需要持续调用、按量使用的场景。想先验证模型效果,可以直接在模型对话页面试,地址 https://taotoken.net/chat 。接入文档在 https://taotoken.net/doc ,里面有完整的请求示例和参数说明。
这里要强调一点:TaoToken 是正规的 API 接入服务,不是让你去搞什么网络绕行。你只需要在代码里配置好 Base URL 和 Key,走标准的 HTTPS 请求就行。QT 的 QNetworkAccessManager 原生支持 HTTPS,不需要额外装什么东西。
把 Key 拿到手之后,建议先别急着写进 QT 代码,而是用 curl 或者 Postman 发一个最简单的请求,确认通道是通的。命令大概长这样:
curl -X POST https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer 你的APIKey" \ -H "Content-Type: application/json" \ -d '{ "model": "你的ModelID", "messages": [{"role": "user", "content": "把这句话润色一下:今天天气不错"}] }'如果返回里有choices字段和正常的文本内容,说明通道没问题。这一步验证过了,再往 QT 里集成,能省掉很多排查时间。很多人一上来就在 QT 里调,结果报错分不清是网络问题还是代码问题,先用命令行确认通道,是最省事的做法。
3. 可复制配置:工具栏动作绑定与格式合并完整代码
这一节是全文的核心,直接给可复制的代码。先看头文件里需要声明什么。全局变量 boldcheck、Italiccheck、UnderLinecheck 用来记录加粗、倾斜、下划线的上一个状态,初始值设为 0。mergeFormat 函数负责把格式合并到光标选区。槽函数按你的动作命名对应即可。
// mainwindow.h public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); int boldcheck, Italiccheck, UnderLinecheck; // 记录加粗、倾斜、下划线的上一个状态 private slots: void mergeFormat(QTextCharFormat fmt); // 将格式合并到光标选区 void on_action_font_triggered(); // 字体 void on_action_fontSize_triggered(); // 字号 void on_action_fontColor_triggered(); // 字体颜色 void on_action_bold_triggered(); // 加粗 void on_action_italic_triggered(); // 倾斜 void on_action_underline_triggered(); // 下划线 void on_action_bgColor_triggered(); // 文字背景色构造函数里把三个状态变量初始化,同时把工具栏按钮和槽函数连起来。如果你用的是 Qt Designer 的 action 自动连接,命名对上就行;如果是手写 connect,参考下面。
// mainwindow.cpp 构造函数片段 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); boldcheck = 0; Italiccheck = 0; UnderLinecheck = 0; connect(ui->action_bold, &QAction::triggered, this, &MainWindow::on_action_bold_triggered); connect(ui->action_italic, &QAction::triggered, this, &MainWindow::on_action_italic_triggered); connect(ui->action_underline, &QAction::triggered, this, &MainWindow::on_action_underline_triggered); }mergeFormat 是整个模块的地基。它的逻辑是:拿到当前光标,如果没有选区,就自动选中光标所在的单词,然后把格式合并进去。这样即使用户没选文字,只把光标放在某个词上,点加粗也能生效。
void MainWindow::mergeFormat(QTextCharFormat fmt) { QTextCursor cursor = ui->textEdit->textCursor(); if (!cursor.hasSelection()) { cursor.select(QTextCursor::WordUnderCursor); } cursor.mergeCharFormat(fmt); ui->textEdit->mergeCurrentCharFormat(fmt); }注意最后那行mergeCurrentCharFormat,它的作用是让编辑器记住当前格式,这样你接着输入的新文字也会沿用这个格式。少了这行,你会发现选中加粗后,继续打字又变回普通体了。
字体和字号用 QFontDialog 和 QFont 来处理。字体对话框返回一个 QFont 对象,直接 setCurrentFont 即可。字号可以单独弹一个输入框,或者用 QFontDialog 里带的字号选择。
void MainWindow::on_action_font_triggered() { bool ok; QFont font = QFontDialog::getFont(&ok, QFont("Microsoft YaHei", 12), this, "选择字体"); if (ok) { ui->textEdit->setCurrentFont(font); } } void MainWindow::on_action_fontSize_triggered() { bool ok; int size = QInputDialog::getInt(this, "设置字号", "字号:", 12, 1, 200, 1, &ok); if (ok) { QTextCharFormat fmt; fmt.setFontPointSize(size); mergeFormat(fmt); } }字体颜色用 QColorDialog,拿到颜色后调 setTextColor。这里注意,setTextColor 作用于当前选区,和 mergeFormat 效果类似,但它是 QTextEdit 的便捷方法。
void MainWindow::on_action_fontColor_triggered() { QColor color = QColorDialog::getColor(Qt::black, this, "选择字体颜色"); if (color.isValid()) { ui->textEdit->setTextColor(color); } }加粗、倾斜、下划线这三个开关型按钮,逻辑是:根据全局变量的当前值决定设置成什么,设置完把变量翻转。这样第一次点变粗,第二次点变正常。
void MainWindow::on_action_bold_triggered() { QTextCharFormat fmt; fmt.setFontWeight(boldcheck ? QFont::Normal : QFont::Bold); mergeFormat(fmt); boldcheck = !boldcheck; } void MainWindow::on_action_italic_triggered() { QTextCharFormat fmt; fmt.setFontItalic(Italiccheck ? false : true); mergeFormat(fmt); Italiccheck = !Italiccheck; } void MainWindow::on_action_underline_triggered() { QTextCharFormat fmt; fmt.setFontUnderline(UnderLinecheck ? false : true); mergeFormat(fmt); UnderLinecheck = !UnderLinecheck; }文字背景色是这一弹新增的重点。很多人会误用 QPalette 去改 textEdit 的背景,那样改的是整个编辑区,不是选中文字的背景。正确做法是用 QTextCharFormat 的 setBackground。
void MainWindow::on_action_bgColor_triggered() { QColor color = QColorDialog::getColor(Qt::yellow, this, "选择文字背景色"); if (color.isValid()) { QTextCharFormat fmt; fmt.setBackground(color); mergeFormat(fmt); } }如果你确实想改整个编辑器的背景色,那才用 QPalette,代码是这样:
QPalette palette = ui->textEdit->palette(); palette.setColor(QPalette::Base, color); ui->textEdit->setPalette(palette);这两者的区别一定要分清:setBackground 是给文字加底色,QPalette::Base 是给编辑区换底色。做富文本格式菜单,你要的是前者。
4. 验证请求与成功结果:逐项点击验证清单
代码写完了,怎么确认每一项都生效且互不干扰?我整理了一份逐项点击验证清单,你照着点一遍,基本能覆盖所有边界情况。
第一步,验证字体族。在编辑器里输入「测试字体样式」六个字,全选,点字体按钮,选一个和默认明显不同的字体,比如从微软雅黑换成楷体。点确定后,选中文字应该立刻变字体。把光标移到这段文字后面继续打字,新打的字应该也是楷体,这说明 mergeCurrentCharFormat 生效了。
第二步,验证字号。选中同一段文字,点字号,输入一个明显大的值比如 28。文字应该变大。这里注意观察,字号变化不应该影响字体族,也不应该影响颜色。
第三步,验证字体颜色。选中文字,点字体颜色,选红色。文字变红。此时加粗、倾斜状态应该保持不变。
第四步,验证加粗。选中文字,点加粗,文字变粗。再点一次,变回正常。连续点三次,应该是粗、正常、粗。如果第三次没变粗,说明 boldcheck 翻转逻辑有问题。
第五步,验证倾斜。选中文字,点倾斜,文字倾斜。再点加粗,此时应该是又粗又斜。再点倾斜取消,应该只剩加粗。这一步专门验证加粗和倾斜的叠加与分离。
第六步,验证下划线。选中文字,点下划线,出现下划线。再叠加倾斜,应该是斜体带下划线。逐项取消,确认互不影响。
第七步,验证文字背景色。选中文字,点背景色,选黄色。文字应该有黄色底色,而不是整个编辑区变黄。这是最容易出错的一项,重点确认。
第八步,验证无选区情况。把光标放在某个词中间,不选中任何文字,直接点加粗。按照 mergeFormat 的逻辑,应该自动选中光标所在单词并加粗。如果你不想要这个行为,可以把cursor.select(QTextCursor::WordUnderCursor)那行去掉,改成直接返回。
第九步,验证混合格式。输入一段话,前半段加粗,后半段倾斜,中间一段加下划线加背景色。全选后点字体颜色变蓝。理想结果是所有文字变蓝,但各自的加粗、倾斜、下划线、背景色都保留。这验证的是 mergeCharFormat 只合并颜色属性、不动其他属性的特性。
第十步,验证保存后重新打开。把带格式的文本保存成 HTML 或 RTF,重新打开,格式应该还在。如果你保存成纯 txt,格式会丢,这是正常的,纯文本不存格式信息。
这份清单走完,你的富文本格式模块基本就稳了。如果哪一项没过,对照下一节的排查表找原因。
5. 本篇常见错误排查:401、local proxy failed、reading choices 与 OAuth
虽然这一篇主体是 QT 富文本格式,但既然提到了 TaoToken 统一 Key 通道,就把接入 AI 能力时最容易撞上的几个报错一起讲了。这些报错在你把编辑器接上模型接口之后大概率会遇到。
第一个,401 Unauthorized。这个最直接,就是 Key 不对或者没带。检查你的请求头是不是Authorization: Bearer <Key>,注意 Bearer 和 Key 之间有一个空格。另外确认 Key 没有多余的空格或换行,从控制台复制的时候容易带上。如果你把 Key 写在了配置文件里,检查读取的时候有没有把引号也读进去。
第二个,local proxy failed。这个报错通常出现在你本地配了什么网络转发工具的情况下。QT 的 QNetworkAccessManager 默认会读取系统代理设置,如果你系统里配了一个不可用的转发,请求就会失败。解决办法是在代码里显式设置不使用代理,或者检查系统网络设置里有没有残留的转发配置。注意,这里说的是排查本地网络配置问题,不是让你去搞什么特殊通道,标准 HTTPS 请求直连即可。
第三个,reading choices 相关报错,比如Cannot read property 'choices' of undefined或者解析响应时拿不到 choices 字段。这通常说明请求发出去了,但返回的不是预期的 JSON 结构。可能原因有三个:一是 Model ID 填错了,服务端返回了错误信息而不是正常响应;二是请求体格式不对,比如 messages 数组写错了;三是响应还没解析完就去读字段了。排查方法是在 QT 里把原始响应打印出来看,用qDebug() << reply->readAll(),先看返回的到底是什么。
第四个,OAuth 相关报错。如果你用的是某些需要 OAuth 流程的模型服务,可能会遇到 token 过期或者 scope 不对的问题。TaoToken 用的是 API Key 方式,不涉及 OAuth 跳转,所以如果你看到 OAuth 报错,先确认自己是不是把 Base URL 填成了别的服务的地址。Base URL 应该是https://taotoken.net/api,不要多加路径。
第五个,Codex 的 auth.json 配置问题。如果你在用 Codex 类的编码工具,它的鉴权信息存在 auth.json 里。这个文件里需要填 Base URL、Key 和 Model ID 三件套。常见错误是 Base URL 末尾多加了斜杠,或者 Model ID 写成了显示名称而不是实际 ID。检查的时候把这三项逐一核对。
第六个,CC Switch 或 Cline MCP 配置问题。这两个工具在配置模型通道时,同样需要 Base URL、Key、Model ID 三件套。CC Switch 的配置文件通常是 JSON 格式,Cline 的 MCP 配置在 settings 里。如果你在这两个工具里遇到连接失败,先确认三件套是否齐全,再确认 JSON 格式有没有语法错误,比如多余的逗号或者少了引号。
把上面这些报错对照一遍,基本能覆盖接入阶段 90% 的问题。剩下的 10% 大概率是网络环境或者服务端临时波动,隔几分钟重试即可。
6. 从格式模块到 AI 润色:下一步怎么走
富文本格式模块跑通之后,你的 QT 文本编辑器已经具备了「所见即所得」的编辑能力。接下来最自然的延伸,就是在工具栏上加一个「AI 润色」按钮。点击后,把当前选中的文字取出来,通过 TaoToken 的接口发给模型,把返回的润色结果替换回选区。
取选中文字用ui->textEdit->textCursor().selectedText(),替换回去用cursor.insertText(结果)。发送请求的部分用 QNetworkAccessManager,POST 到https://taotoken.net/api/chat/completions,请求头带 Key,请求体里 model 填你的 Model ID,messages 里放一段系统提示词加用户选中的文字。
如果你想让编辑器支持多种模型切换,可以在设置里做一个下拉框,把不同的 Model ID 列进去,用户选哪个就用哪个。因为 TaoToken 是统一 Key 通道,你不需要为每个模型单独配 Key,换 Model ID 就行。想先试试模型效果,可以去 https://taotoken.net/chat 直接对话验证。接入文档在 https://taotoken.net/doc ,里面有完整的参数说明。需要生成 Key 就去 https://taotoken.net/api-keys 。长期做编码辅助的话,Coding Plan 在 https://taotoken.net/coding-plan 。
最后说一个我踩过的坑:在 QT 里发 HTTPS 请求,如果遇到 SSL 握手失败,先检查你的 QT 版本有没有带 OpenSSL 库。Windows 下 QT 默认可能不带,需要手动把 libssl 和 libcrypto 的动态库放到可执行文件目录。这个和 TaoToken 无关,是 QT 本身的依赖问题,但很多人会误以为是接口不通,白白排查半天。确认方法很简单,写一个最小的 QNetworkAccessManager 请求任意 HTTPS 地址,如果也失败,那就是 SSL 库的问题。