这两个 demo 都围绕"内容动态变化的文本区"展开:一个是输入时左侧 Markdown 实时渲染右侧预览,一个是点击按钮追加文本时滚动条自动跟随到底端。两者的共同难点是处理"内容增长后视图怎么响应"------一个靠 Flickable 同步滚动,一个靠 ScrollBar.position 声明式绑定。
这篇介绍两个 demo:
- MarkDown 编辑器 --- 左右分栏,左侧输入 Markdown,右侧用
Text.MarkdownText实时渲染 - 保持滚动 --- 点击按钮追加文本,垂直滚动条
position始终保持在底端
MarkDown 编辑器
这个 demo 做一个简易 Markdown 编辑器:左侧 TextArea 输入 Markdown 源文,右侧 Text 用 textFormat: Text.MarkdownText 实时渲染。外层用 Flickable + SplitView 解决两栏同步滚动。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
// MarkDown编辑器:左侧输入 Markdown,右侧实时预览渲染结果
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 10
TitleSeparator {
title: "MarkDown 编辑器"
description: "左侧输入 Markdown 文本,右侧以 Text.MarkdownText 实时渲染预览"
}
// 解决了同步滚动的问题
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: parent.width
contentHeight: Math.max(markdownPreview.implicitHeight,
markdownInput.implicitHeight,
parent.height)
clip: true
ScrollBar.vertical: ScrollBar {}
SplitView {
anchors.fill: parent
// Markdown 输入区域
TextArea {
id: markdownInput
SplitView.preferredWidth: parent.width / 2
placeholderText: "输入 Markdown 文本..."
font.family: "Courier New"
wrapMode: TextArea.Wrap
background: Rectangle {
color: markdownInput.enabled ? "white" : "#f5f5f5"
border.color: markdownInput.activeFocus ? "#1296FF" : "#c0c0c0"
border.width: markdownInput.activeFocus ? 2 : 1
radius: 4
}
}
// Markdown 预览区域
Text {
id: markdownPreview
SplitView.fillWidth: true
wrapMode: Text.Wrap
textFormat: Text.MarkdownText
text: markdownInput.text
padding: 10
onLinkActivated: Qt.openUrlExternally(link)
}
}
}
}
}
关键逻辑解析
Text.MarkdownText:原生 Markdown 渲染
Qt 6 的 Text 控件内置 Markdown 支持,把 textFormat 设成 Text.MarkdownText 即可渲染 CommonMark 子集:
- 标题:
#到###### - 强调:
**加粗**、*斜体*、~~删除线~~ - 列表:
- 无序列表、1. 有序列表 - 链接:
[文本](url) - 代码:
行内代码、代码块 - 引用:
> 引用块 - 分隔线:
---
相比第 1 篇用 TextArea.RichText 解析 HTML 子集,MarkdownText 直接吃 Markdown 源文,不用先转 HTML,写起来更直接。前提是 Qt 版本 ≥ 6(早期 5.x 需要 QtMarkdown 第三方库)。
左右双栏的实时联动
qml
text: markdownInput.text
右侧 Text 的 text 绑定到左侧 TextArea.text,输入即渲染。这是声明式绑定的典型用法------不需要 onTextChanged 信号手动同步,QML 绑定机制自动处理。
Flickable 解决两栏同步滚动
两栏内容长度不一致时(Markdown 源文短、渲染后可能更长,或反过来),需要同步滚动。这个 demo 的做法是把 SplitView 放进 Flickable 里:
contentHeight取markdownPreview.implicitHeight、markdownInput.implicitHeight、parent.height三者最大值,保证Flickable的内容区能容纳较长的一栏。- 滚动
Flickable时,SplitView整体上下移动,两栏同步滚动。 ScrollBar.vertical: ScrollBar {}给Flickable加可见滚动条。
这是"整体滚动"思路------两栏作为整体一起滚。如果要做"两栏独立滚动但位置同步"(像 Typora 的编辑预览模式),需要在 onContentYChanged 里手动同步另一栏的 contentY,复杂度高很多。
SplitView 的分栏
SplitView 让两栏可拖拽分隔条调整宽度:
SplitView.preferredWidth: parent.width / 2:左侧初始占一半。SplitView.fillWidth: true:右侧填满剩余宽度。
拖动中间分隔条会动态调整两栏比例,适合"编辑/预览"场景------写代码时拉宽输入区,看效果时拉宽预览区。
onLinkActivated 处理链接点击
qml
onLinkActivated: Qt.openUrlExternally(link)
Markdown 里的 [文本](url) 渲染成可点击链接,点击时触发 onLinkActivated 信号,Qt.openUrlExternally(link) 调系统默认浏览器打开链接。不加这个处理,链接点了没反应。
font.family: "Courier New"
输入区用等宽字体,让 Markdown 的 #、-、* 等符号对齐,写起来更像代码编辑器。预览区用默认字体,渲染后的标题、正文各有样式。
适用场景
任何需要"编辑 + 预览"双模式的场景:
- 笔记应用:输入 Markdown,预览排版效果
- 文档编辑器:写 README、API 文档时实时预览
- 富文本输入:用 Markdown 语法代替工具栏按钮,输入更快
把输入区换成 TextField + 自动补全,能做命令行式的 Markdown 快速输入。
保持滚动
这个 demo 模拟日志输出场景:点击按钮追加文本,垂直滚动条 position 始终保持在底端,新内容自动滚动到可视区域。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
// 保持滚动:点击按钮追加文本,垂直滚动条始终保持滚动到底端
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 10
TitleSeparator {
title: "保持滚动"
description: "点击按钮追加文本,垂直滚动条 position 始终保持在底端"
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Button {
text: "追加文本"
onClicked: {
textArea.append("【追加文本】这是一个测试文本,用于演示滚动功能。\n".repeat(5))
}
}
Label {
text: "已追加文本,字符数: " + textArea.text.length
color: "#666"
font.pixelSize: 12
}
}
ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
ScrollBar.vertical: ScrollBar {
anchors.top: scrollView.top
anchors.bottom: scrollView.bottom
anchors.right: scrollView.right
policy: ScrollBar.AlwaysOn
// 保持滚动到底端
position: {
if (scrollView.height > 0 && scrollView.contentHeight > scrollView.height) {
return (scrollView.contentHeight - scrollView.height) / scrollView.contentHeight;
}
return 0;
}
}
TextArea {
id: textArea
wrapMode: TextArea.Wrap
text: "这是一个测试文本,用于演示滚动功能。\n".repeat(30)
selectByMouse: true
mouseSelectionMode: TextEdit.SelectCharacters
background: Rectangle {
color: textArea.enabled ? "white" : "#f5f5f5"
border.color: textArea.activeFocus ? "#1296FF" : "#c0c0c0"
border.width: textArea.activeFocus ? 2 : 1
radius: 4
}
}
}
}
}
关键逻辑解析
TextArea.append() 追加文本
qml
textArea.append("【追加文本】这是一个测试文本,用于演示滚动功能。\n".repeat(5))
append(text) 是 TextArea 的方法,在末尾追加文本并自动换行。和直接改 text 属性的区别:
text = text + newText:整个text重新赋值,文本很长时性能差append(newText):内部只处理追加部分,性能更好,适合日志类持续追加场景
.repeat(5) 是 JS 字符串方法,把文本重复 5 次模拟一段较长输出。
ScrollBar.position 声明式绑定
核心逻辑在 ScrollBar.vertical 里:
qml
position: {
if (scrollView.height > 0 && scrollView.contentHeight > scrollView.height) {
return (scrollView.contentHeight - scrollView.height) / scrollView.contentHeight;
}
return 0;
}
position 是滚动条当前位置,取值 0 到 1 - size(size 是可见部分占比)。把 position 绑定到一个表达式,让滚动条始终指向底端:
scrollView.contentHeight:内容总高度scrollView.height:可视区高度(contentHeight - height) / contentHeight:底端位置------内容超出可视区时,这个值就是滚动到底部所需的position
这是声明式绑定------contentHeight 或 height 变化时,position 自动重算。追加文本后 contentHeight 增大,position 跟着变大,滚动条自动滑到底端。
为什么不用 onContentHeightChanged 信号
也可以写成命令式:
qml
onContentHeightChanged: {
if (contentHeight > height) {
scrollBar.position = (contentHeight - height) / contentHeight
}
}
但声明式绑定更简洁,也避免了"信号触发顺序"的坑。QML 里能用绑定就用绑定,需要复杂计算或副作用时才用信号。
policy: ScrollBar.AlwaysOn
滚动条始终显示,不自动隐藏。日志场景下用户需要持续看到滚动条位置,AlwaysOn 比 AsNeeded(按需显示)更合适。
anchors 固定滚动条位置
qml
anchors.top: scrollView.top
anchors.bottom: scrollView.bottom
anchors.right: scrollView.right
ScrollBar 默认会自动定位,但显式 anchors 能确保滚动条贴在 ScrollView 右侧并占满高度,避免某些主题下位置偏移。
适用场景
任何"持续输出内容 + 自动跟随到底部"的场景:
- 日志查看器:后台持续打印日志,滚动条始终在底部
- 聊天窗口:新消息追加后自动滚动到最新
- 控制台输出:命令行输出实时显示
- 构建日志:编译过程输出持续追加
反过来,如果用户手动往上滚动查看历史,应该停止自动跟随------这时需要加个标志位判断"用户是否在底部",只有靠近底部时才自动滚动。
两个 demo 对比
| 维度 | MarkDown 编辑器 | 保持滚动 |
|---|---|---|
| 内容变化方式 | 输入框实时编辑 | 按钮触发 append() |
| 视图响应 | Flickable 整体同步滚动 |
ScrollBar.position 绑定到底端 |
| 核心机制 | Text.MarkdownText 渲染 |
声明式属性绑定 |
| 典型场景 | 编辑 + 预览双栏 | 日志、聊天持续输出 |
两个 demo 分别代表"内容编辑"和"内容追加"两种动态文本场景。前者靠 Flickable 同步两栏滚动,后者靠 ScrollBar.position 绑定让滚动条自动跟随。理解了这两种模式,遇到"内容动态变化的文本区"基本能举一反三。
运行验证
- Qt Creator 打开
qml_textarea/CMakeLists.txt - 按
Ctrl+R运行 - 在左侧导航切换"MarkDown 编辑器"、"保持滚动"两个 demo
扩展复用方向
- MarkDown 编辑器加"导出 HTML"按钮:用
markdownPreview.text拿到渲染后的 HTML,写入文件 - MarkDown 编辑器加语法高亮:输入区换成支持 Markdown 语法高亮的组件(需要自己实现或用第三方库)
- 保持滚动加"用户滚动检测":监听
ScrollBar.position手动变化,用户往上滚时停止自动跟随,回到底部后恢复 - 保持滚动改成"追加后平滑动画滚动到底部":用
NumberAnimation动画position从当前值到 1,过渡更自然 - 把保持滚动封装成
LogView.qml组件,提供append(text)方法,外部只管追加内容,滚动跟随逻辑内部处理
已验证环境:
- Qt 版本:Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_textarea