上一篇讲了 TextArea 的基础输入和字体颜色(见系列第 1 篇),这篇讲内容怎么显示:HTML 富文本的实时渲染,以及三种换行模式的区别。
这篇介绍两个 demo:
- 富文本 --- 上方纯文本输入,下方富文本实时渲染对比
- 换行模式 --- 按钮切换
NoWrap、Wrap、WrapAnywhere三种模式
富文本
这个 demo 用两个 TextArea 上下对比:上方用 PlainText 输入 HTML 标签原文,下方用 RichText 渲染同样的标签,实时预览效果。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
// 富文本:对比纯文本与富文本两种 textFormat 的渲染效果
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 10
TitleSeparator {
title: "富文本"
description: "上方为纯文本输入区,下方以 RichText 格式实时渲染 HTML 标签"
}
TextArea {
id: textArea
Layout.fillWidth: true
Layout.minimumHeight: 120
textFormat: TextArea.PlainText
wrapMode: TextArea.Wrap
text: "<h1>富文本示例</h1><p>这是<b>加粗</b>文本,这是<i>斜体</i>文本。</p><ul><li>列表项1</li><li>列表项2</li></ul>"
background: Rectangle {
color: textArea.enabled ? "white" : "#f5f5f5"
border.color: textArea.activeFocus ? "#1296FF" : "#c0c0c0"
border.width: textArea.activeFocus ? 2 : 1
radius: 4
}
}
TextArea {
id: richView
Layout.fillWidth: true
Layout.fillHeight: true
textFormat: TextArea.RichText
wrapMode: TextArea.Wrap
text: textArea.text
readOnly: true
background: Rectangle {
color: "#fafafa"
border.color: "#c0c0c0"
border.width: 1
radius: 4
}
onTextChanged: {
if (textFormat === TextEdit.RichText) {
console.log("当前内容为富文本格式")
}
}
}
}
}
关键逻辑解析
textFormat 两种取值
TextArea.PlainText:把text当纯文本处理,HTML 标签原样显示。上方输入框用的就是这个,输入<b>加粗</b>显示原文。TextArea.RichText:解析text里的 HTML 子集标签并渲染样式。下方只读框用 RichText,把同样的内容渲染成加粗、斜体、列表。
双框联动
核心是 text: textArea.text 这一行------把下方渲染框的 text 绑定到上方输入框。上方输入时下方实时刷新,是个简易的"富文本编辑器预览"模式。
支持的 HTML 子集
Qt 的 RichText 不是完整 HTML,支持的有用标签包括:
- 标题:
<h1>到<h6> - 文本样式:
<b>加粗、<i>斜体、<u>下划线、<s>删除线 - 段落与换行:
<p>、<br> - 列表:
<ul><li>、<ol><li> - 颜色:
<span style="color:red"> - 链接:
<a href="...">
readOnly + 不同背景区分
下方渲染框加了 readOnly: true,背景用 #fafafa 浅灰和上方白色输入框区分,视觉上明确"这里是预览区不可编辑"。
适用场景
任何需要实时预览的场景:富文本编辑器、Markdown 编辑器预览、HTML 邮件模板编辑。把上方输入框换成 Markdown 解析器,下方用 RichText 渲染解析后的 HTML,就是简易 Markdown 编辑器(见本系列后续篇)。
换行模式
这个 demo 用三个按钮切换 wrapMode 三种模式,配合一个 Label 实时显示当前模式,对比不同模式下的文本断行行为。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
// 换行模式:演示 TextArea.NoWrap / Wrap / WrapAnywhere 三种换行模式
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
TitleSeparator {
title: "换行模式"
description: "通过按钮切换 NoWrap、Wrap、WrapAnywhere 三种换行模式"
}
TextArea {
id: wrapTextArea
Layout.fillWidth: true
Layout.fillHeight: true
text: "TextArea is not scrollable by itself. Especially on screen-size constrained platforms, it is often preferable to make entire application pages scrollable. On such a scrollable page, a non-scrollable TextArea might behave better than nested scrollable controls."
placeholderText: "选择换行模式..."
clip: true
selectByMouse: true
background: Rectangle {
color: wrapTextArea.enabled ? "white" : "#f5f5f5"
border.color: wrapTextArea.activeFocus ? "#1296FF" : "#c0c0c0"
border.width: wrapTextArea.activeFocus ? 2 : 1
radius: 4
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
Button {
text: "不换行"
onClicked: {
wrapTextArea.wrapMode = TextArea.NoWrap
// 让 wrapMode 立即生效
wrapTextArea.width = wrapTextArea.width - 1
wrapTextArea.width = wrapTextArea.width + 1
}
}
Button {
text: "单词换行"
onClicked: {
wrapTextArea.wrapMode = TextArea.Wrap
wrapTextArea.width = wrapTextArea.width - 1
wrapTextArea.width = wrapTextArea.width + 1
}
}
Button {
text: "任意位置换行"
onClicked: {
wrapTextArea.wrapMode = TextArea.WrapAnywhere
wrapTextArea.width = wrapTextArea.width - 1
wrapTextArea.width = wrapTextArea.width + 1
}
}
Label {
text: {
switch (wrapTextArea.wrapMode) {
case TextArea.NoWrap: return "当前模式: 不换行"
case TextArea.Wrap: return "当前模式: 单词换行"
case TextArea.WrapAnywhere: return "当前模式: 任意位置换行"
default: return "当前模式: 未知"
}
}
color: "#1296FF"
font.bold: true
}
}
}
}
关键逻辑解析
三种换行模式
wrapMode 决定超长文本怎么断行:
| 模式 | 行为 | 适用场景 |
|---|---|---|
NoWrap |
不换行,文本横向溢出 | 代码编辑器、单行密文 |
Wrap |
按单词边界换行,不拆词 | 段落正文、说明文本 |
WrapAnywhere |
任意位置断行,单词可被拆 | 紧凑显示、长 URL 防溢出 |
Wrap 是大多数场景的默认选择,保证英文单词完整可读;WrapAnywhere 适合显示超长无空格文本(比如 URL、哈希值),保证内容不溢出控件。
手动触发布局刷新的 hack
onClicked 里有这两行:
qml
wrapTextArea.width = wrapTextArea.width - 1
wrapTextArea.width = wrapTextArea.width + 1
这是 QML 里手动触发重新布局的常见技巧。某些 Qt 版本里直接改 wrapMode 不会立即重算文本布局,需要让宽度先减后加触发 widthChanged 信号,强制 TextArea 重排版。如果改 wrapMode 后发现切换没生效,加上这两行就稳了。
Label 用 switch 显示当前模式
Label 的 text 用 switch 语句匹配 wrapTextArea.wrapMode,把枚举值转成中文显示。这是个声明式绑定------wrapMode 变化时 Label 自动刷新,不用手动 onClicked 更新。
三种换行模式对比
| 维度 | NoWrap | Wrap | WrapAnywhere |
|---|---|---|---|
| 文本断行 | 不换行 | 单词边界 | 任意字符 |
| 单词完整性 | 完整 | 完整 | 可能被拆 |
| 横向溢出 | 是 | 否 | 否 |
| 典型场景 | 代码、单行 | 段落、说明 | URL、长哈希 |
Wrap 是默认首选;需要横向滚动看长行用 NoWrap;要塞下超长无空格文本用 WrapAnywhere。
运行验证
- Qt Creator 打开
qml_textarea/CMakeLists.txt - 按
Ctrl+R运行 - 在左侧导航切换"富文本"、"换行模式"两个 demo
- 富文本:上方输入 HTML 标签看下方实时渲染(试改
<b>为<u>看下划线) - 换行模式:点三个按钮看文本断行行为变化、看右下角
Label显示当前模式
扩展复用方向
- 富文本 demo 扩展成 Markdown 编辑器:用
Markdown解析库(或自己写正则)把 Markdown 转 HTML,再赋值给 RichText 渲染框 - 富文本加
onLinkActivated处理<a>链接点击,实现预览区可点链接跳转 - 换行模式加
Text.WordWrap和Text.WrapAtWordBoundaryOrAnywhere对比,理解和TextArea.Wrap系列的差异 - 把换行模式 demo 的
wrapMode切换封装成带默认值的属性,复用到其他TextArea上
已验证环境:
- Qt 版本:Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_textarea