Qt for HarmonyOS TextEditorPro 多功能文本编辑器开源鸿蒙开发实践
本文介绍了一个基于Qt框架开发的HarmonyOS多功能文本编辑器TextEditorPro。该项目实现了丰富的文本编辑功能,包括样式控制、颜色选择、查找替换、文本格式化等特性。项目采用Qt Quick Controls 2.15开发,展示了在HarmonyOS平台上使用Qt开发应用的具体实践。文档详细说明了HarmonyOS特有的开发注意事项,如必须使用qtmain()作为入口函数、OpenGL
Qt for HarmonyOS TextEditorPro 多功能文本编辑器开源鸿蒙开发实践
📋 项目概述

本文档基于一个完整的 TextEditorPro 项目,详细介绍了如何在 HarmonyOS 平台上使用 Qt 开发多功能文本编辑器应用程序。项目整合了文本编辑、样式控制、查找替换、格式化等功能,展示了 Qt Quick Controls 2.15 在 HarmonyOS 平台上的实际应用,为开发者提供了完整的文本编辑器开发参考。
项目地址:https://gitcode.com/szkygc/HarmonyOs_PC-PGC/blob/main/TextEditorPro
✨ 主要功能
- ✅ 文本编辑:支持多行文本编辑,自动换行
- ✅ 样式控制:下划线、斜体、粗体三种文本样式
- ✅ 颜色控制:黑色、红色、蓝色三种文本颜色
- ✅ 查找替换:支持查找、替换、全部替换功能
- ✅ 文本操作:追加行、逐行读取、全部读取、清空文本
- ✅ 文本格式化:转大写、转小写、首字母大写、去除空格、插入时间
- ✅ 文本统计:实时统计字符数、行数、词数
- ✅ 对话框:查找结果对话框、全部读取对话框
- ✅ 响应式布局:适配不同屏幕尺寸
- ✅ 完整的触摸交互支持
🛠️ 技术栈
- 开发框架: Qt 5.15+ for HarmonyOS
- 编程语言: C++ / QML / JavaScript
- 界面框架: Qt Quick Controls 2
- 文本组件: TextArea / TextInput
- 构建工具: CMake
- 目标平台: HarmonyOS (OpenHarmony) / PC
🏗️ 项目架构
目录结构
TextEditorPro/
├── entry/src/main/
│ ├── cpp/
│ │ ├── main.cpp # 应用入口(HarmonyOS适配)
│ │ ├── main.qml # 主界面(多功能文本编辑器)
│ │ ├── CMakeLists.txt # 构建配置
│ │ └── qml.qrc # QML资源文件
│ ├── module.json5 # 模块配置
│ └── resources/ # 资源文件
└── image/
└── 运行截图.png # 运行截图
组件层次结构
ApplicationWindow (main.qml)
├── Rectangle (内容容器)
│ └── ScrollView (滚动视图)
│ └── Column (主列布局)
│ ├── Row (标题和统计信息)
│ ├── Row (样式控制 - 三个CheckBox)
│ ├── Row (颜色控制 - 三个RadioButton)
│ ├── Rectangle (文本编辑区域)
│ │ └── ScrollView
│ │ └── TextArea (文本编辑器)
│ ├── Column (查找替换区域)
│ │ ├── Text (标题)
│ │ └── Row
│ │ ├── Column (查找输入框)
│ │ ├── Column (替换输入框)
│ │ └── Column (操作按钮)
│ ├── Column (文本操作区域)
│ │ ├── Text (标题)
│ │ └── Row (操作按钮 + ComboBox)
│ ├── Column (文本格式化区域)
│ │ ├── Text (标题)
│ │ └── Row (格式化按钮)
│ └── Row (确定/取消/退出按钮)
├── Dialog (全部读取对话框)
│ ├── ScrollView
│ │ └── TextArea (只读文本显示)
│ └── DialogButtonBox (确定/取消按钮)
└── Dialog (查找结果对话框)
├── Text (结果显示)
└── DialogButtonBox (确定按钮)
📝 核心功能实现
1. HarmonyOS 入口函数:qtmain()
⚠️ 关键要点:HarmonyOS 真机上必须使用 qtmain() 而不是 main()!
// ✅ 正确写法
extern "C" int qtmain(int argc, char **argv)
{
// Qt 应用作为共享库加载,生命周期由 HarmonyOS 管理
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec(); // ⚠️ 重要:必须调用 exec() 启动事件循环
}
// ❌ 错误写法(桌面应用方式)
int main(int argc, char *argv[])
{
// 这种方式在 HarmonyOS 上会导致应用无法正常启动
}
原因说明:
- HarmonyOS 将 Qt 应用作为共享库(.so)加载
- 应用生命周期由 HarmonyOS 的 Ability 管理
qtmain()是 HarmonyOS Qt 插件的标准入口点
2. OpenGL ES 表面格式配置
⚠️ 关键要点:必须在创建 QGuiApplication 之前配置 QSurfaceFormat!
// Step 1: 配置 OpenGL ES 表面格式(必须在创建应用之前!)
QSurfaceFormat format;
// 设置 Alpha 通道(透明度)
format.setAlphaBufferSize(8); // 8 位 Alpha 通道
// 设置颜色通道(RGBA 32 位真彩色)
format.setRedBufferSize(8); // 8 位红色通道
format.setGreenBufferSize(8); // 8 位绿色通道
format.setBlueBufferSize(8); // 8 位蓝色通道
// 设置深度和模板缓冲区
format.setDepthBufferSize(24); // 24 位深度缓冲
format.setStencilBufferSize(8); // 8 位模板缓冲
// 指定渲染类型为 OpenGL ES(HarmonyOS要求)
format.setRenderableType(QSurfaceFormat::OpenGLES);
// 指定 OpenGL ES 版本为 3.0(推荐)
format.setVersion(3, 0);
// ⚠️ 关键:必须在创建 QGuiApplication 之前设置默认格式!
QSurfaceFormat::setDefaultFormat(format);
// Step 2: 创建 Qt 应用实例(必须在设置格式之后)
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
QGuiApplication app(argc, argv);
配置说明:
- OpenGL ES 3.0:HarmonyOS 推荐使用 OpenGL ES 3.0
- RGBA 8-8-8-8:32 位真彩色,支持透明度
- 深度缓冲 24 位:用于 3D 渲染和层级管理
- 模板缓冲 8 位:用于复杂图形效果
3. 文本样式控制实现
核心思想:使用 CheckBox 控制文本的样式属性(下划线、斜体、粗体)。
ApplicationWindow {
id: root
// 文本样式属性
property bool textUnderline: false
property bool textItalic: false
property bool textBold: false
CheckBox {
id: underlineCheckBox
text: "下划线"
font.pixelSize: 36
checked: textUnderline
// 自定义 indicator 样式
indicator: Rectangle {
implicitWidth: 56
implicitHeight: 56
x: underlineCheckBox.leftPadding
y: parent.height / 2 - height / 2
radius: 8
border.color: underlineCheckBox.checked ? "#0078D4" : "#999999"
border.width: underlineCheckBox.checked ? 4 : 3
color: underlineCheckBox.checked ? "#0078D4" : "white"
Text {
anchors.centerIn: parent
text: "✓"
color: "white"
font.pixelSize: 40
font.bold: true
visible: underlineCheckBox.checked
}
}
onCheckedChanged: {
textUnderline = checked
}
}
TextArea {
id: textEdit
font.underline: textUnderline // 绑定到样式属性
font.italic: textItalic
font.bold: textBold
}
}
关键点:
- 属性绑定:
TextArea的样式属性绑定到ApplicationWindow的属性 - 双向绑定:
CheckBox的checked属性绑定到样式属性 - 自定义样式:使用
indicator自定义CheckBox的外观
4. 文本颜色控制实现
核心思想:使用 RadioButton 控制文本的颜色。
ApplicationWindow {
id: root
// 文本颜色属性(0: 黑色, 1: 红色, 2: 蓝色)
property int textColorIndex: 0
// 颜色映射
property color textColor: {
if (textColorIndex === 0) return "black"
else if (textColorIndex === 1) return "red"
else return "blue"
}
Row {
spacing: 60
RadioButton {
id: blackRadioButton
text: "黑色"
font.pixelSize: 36
checked: textColorIndex === 0
onCheckedChanged: {
if (checked) {
textColorIndex = 0
}
}
}
RadioButton {
id: redRadioButton
text: "红色"
font.pixelSize: 36
checked: textColorIndex === 1
onCheckedChanged: {
if (checked) {
textColorIndex = 1
}
}
}
RadioButton {
id: blueRadioButton
text: "蓝色"
font.pixelSize: 36
checked: textColorIndex === 2
onCheckedChanged: {
if (checked) {
textColorIndex = 2
}
}
}
}
TextArea {
id: textEdit
color: textColor // 绑定到颜色属性
}
}
关键点:
- 索引映射:使用
textColorIndex作为颜色索引 - 颜色计算:使用 JavaScript 表达式计算颜色值
- 单选组:
RadioButton自动形成单选组
5. TextArea 文本编辑器实现
核心思想:使用 TextArea 实现多行文本编辑,支持样式和颜色绑定。
Rectangle {
width: parent.width
height: 500
color: "white"
border.color: "#333333"
border.width: 5
radius: 12
ScrollView {
anchors.fill: parent
anchors.margins: 5
TextArea {
id: textEdit
width: parent.width
height: Math.max(500, implicitHeight)
placeholderText: "在此输入文本..."
text: "Hello world\n\nIt is my demo."
font.pixelSize: 38
font.underline: textUnderline
font.italic: textItalic
font.bold: textBold
color: textColor
wrapMode: TextArea.Wrap
selectByMouse: true
padding: 20
selectionColor: "#0078D4"
selectedTextColor: "white"
placeholderTextColor: "#999999"
onTextChanged: {
updateTextStats() // 文本变化时更新统计
}
// 背景样式
background: Rectangle {
color: "white"
border.color: textEdit.activeFocus ? "#0078D4" : "transparent"
border.width: 2
radius: 8
}
}
}
}
关键点:
- 多行编辑:
TextArea支持多行文本编辑 - 自动换行:使用
wrapMode: TextArea.Wrap实现自动换行 - 文本选择:使用
selectByMouse: true支持鼠标选择 - 焦点样式:使用
activeFocus实现焦点时的边框高亮
6. 文本统计功能实现
核心思想:实时统计文本的字符数、行数、词数。
ApplicationWindow {
id: root
// 文本统计属性
property int textLength: 0
property int textLineCount: 0
property int textWordCount: 0
// 更新文本统计
function updateTextStats() {
var text = textEdit.text
textLength = text.length
var lines = text.split("\n")
textLineCount = lines.length
var words = text.trim().split(/\s+/).filter(function(word) { return word.length > 0 })
textWordCount = words.length
}
Row {
Text {
text: "字符: " + textLength + " | 行: " + textLineCount + " | 词: " + textWordCount
font.pixelSize: 26
color: "#666666"
}
}
TextArea {
id: textEdit
onTextChanged: {
updateTextStats() // 文本变化时更新统计
}
}
}
关键点:
- 字符统计:使用
text.length获取字符数 - 行数统计:使用
split("\n")分割文本获取行数 - 词数统计:使用正则表达式
/\s+/分割文本获取词数 - 实时更新:在
onTextChanged中调用统计函数
7. 查找替换功能实现
核心思想:使用 indexOf 和 replace 方法实现文本查找和替换。
ApplicationWindow {
id: root
// 查找替换相关
property string searchText: ""
property string replaceText: ""
// 查找文本
function findText() {
if (searchText.trim() === "") {
findResultDialog.text = "请输入要查找的文本"
findResultDialog.open()
return
}
var text = textEdit.text
var index = text.indexOf(searchText)
if (index >= 0) {
// 选中找到的文本
textEdit.select(index, index + searchText.length)
textEdit.cursorPosition = index + searchText.length
findResultDialog.text = "找到文本: \"" + searchText + "\"\n位置: " + index
} else {
findResultDialog.text = "未找到文本: \"" + searchText + "\""
}
findResultDialog.open()
}
// 替换文本
function replaceText() {
if (searchText.trim() === "") {
findResultDialog.text = "请输入要替换的文本"
findResultDialog.open()
return
}
var text = textEdit.text
if (text.indexOf(searchText) >= 0) {
textEdit.text = text.replace(searchText, replaceText)
updateTextStats()
findResultDialog.text = "已替换: \"" + searchText + "\" -> \"" + replaceText + "\""
} else {
findResultDialog.text = "未找到要替换的文本: \"" + searchText + "\""
}
findResultDialog.open()
}
// 全部替换
function replaceAll() {
if (searchText.trim() === "") {
findResultDialog.text = "请输入要替换的文本"
findResultDialog.open()
return
}
var text = textEdit.text
var count = 0
while (text.indexOf(searchText) >= 0) {
text = text.replace(searchText, replaceText)
count++
}
if (count > 0) {
textEdit.text = text
updateTextStats()
findResultDialog.text = "已全部替换 " + count + " 处: \"" + searchText + "\" -> \"" + replaceText + "\""
} else {
findResultDialog.text = "未找到要替换的文本: \"" + searchText + "\""
}
findResultDialog.open()
}
}
关键点:
- 查找算法:使用
indexOf查找文本位置 - 文本选择:使用
select()方法选中找到的文本 - 单次替换:使用
replace()方法替换第一个匹配项 - 全部替换:使用
while循环替换所有匹配项
8. 文本操作功能实现
核心思想:实现追加行、逐行读取、全部读取、清空等文本操作。
ApplicationWindow {
id: root
// QPlainTextEditDemo 相关属性
property int rowCount: 0 // 行号计数器
// 追加一行文本
function appendLine() {
rowCount++
var newLine = "第" + rowCount + "行"
textEdit.text = textEdit.text + "\n" + newLine
updateTextStats()
}
// 逐行读取,添加到 ComboBox
function readLines() {
lineComboBox.model.clear()
var lines = textEdit.text.split("\n")
for (var i = 0; i < lines.length; i++) {
if (lines[i].trim() !== "") { // 忽略空行
lineComboBox.model.append({text: lines[i]})
}
}
}
// 全部读取,显示对话框
function readAll() {
var allText = textEdit.text
if (allText.trim() === "") {
allText = "文本区域为空"
}
readAllDialog.text = allText
readAllDialog.open()
}
// 清空文本
function clearText() {
textEdit.text = ""
rowCount = 0
lineComboBox.model.clear()
updateTextStats()
}
ComboBox {
id: lineComboBox
width: 280
height: 80
model: ListModel {
id: lineModel
}
}
}
关键点:
- 追加行:使用字符串拼接追加新行
- 逐行读取:使用
split("\n")分割文本,添加到ListModel - 全部读取:获取全部文本,显示在对话框中
- 清空文本:重置文本和计数器
9. 文本格式化功能实现
核心思想:实现转大写、转小写、首字母大写、去除空格、插入时间等格式化功能。
ApplicationWindow {
id: root
// 文本格式化:转大写
function toUpperCase() {
textEdit.text = textEdit.text.toUpperCase()
updateTextStats()
}
// 文本格式化:转小写
function toLowerCase() {
textEdit.text = textEdit.text.toLowerCase()
updateTextStats()
}
// 文本格式化:首字母大写
function toTitleCase() {
var lines = textEdit.text.split("\n")
var result = []
for (var i = 0; i < lines.length; i++) {
var line = lines[i]
if (line.length > 0) {
line = line.charAt(0).toUpperCase() + line.slice(1).toLowerCase()
}
result.push(line)
}
textEdit.text = result.join("\n")
updateTextStats()
}
// 去除首尾空格
function trimText() {
var lines = textEdit.text.split("\n")
var result = []
for (var i = 0; i < lines.length; i++) {
result.push(lines[i].trim())
}
textEdit.text = result.join("\n")
updateTextStats()
}
// 插入当前时间
function insertDateTime() {
var now = new Date()
var dateStr = now.toLocaleString(Qt.locale("zh_CN"), "yyyy-MM-dd hh:mm:ss")
textEdit.text = textEdit.text + "\n" + dateStr
updateTextStats()
}
}
关键点:
- 大小写转换:使用
toUpperCase()和toLowerCase()方法 - 首字母大写:使用
charAt(0)和slice(1)实现 - 去除空格:使用
trim()方法去除首尾空格 - 插入时间:使用
Date对象和toLocaleString()格式化时间
10. 对话框实现
核心思想:使用 Dialog 组件实现查找结果和全部读取对话框。
ApplicationWindow {
id: root
// 全部读取对话框
Dialog {
id: readAllDialog
x: (root.width - width) / 2
y: (root.height - height) / 2
width: Math.min(root.width - 100, 600)
height: Math.min(root.height - 100, 500)
modal: true
title: "文本内容"
property string text: ""
background: Rectangle {
color: "white"
radius: 15
border.color: "#CCCCCC"
border.width: 2
}
contentItem: ScrollView {
width: readAllDialog.width - 40
height: readAllDialog.height - 120
TextArea {
id: dialogTextArea
width: parent.width
readOnly: true
text: readAllDialog.text
font.pixelSize: 24
wrapMode: TextArea.Wrap
selectByMouse: true
}
}
footer: DialogButtonBox {
Button {
text: "确定"
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
onClicked: {
clearText()
readAllDialog.close()
}
}
Button {
text: "取消"
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
onClicked: {
readAllDialog.close()
}
}
}
}
// 查找结果对话框
Dialog {
id: findResultDialog
x: (root.width - width) / 2
y: (root.height - height) / 2
width: Math.min(root.width - 100, 500)
height: 200
modal: true
title: "操作结果"
property string text: ""
contentItem: Text {
text: findResultDialog.text
font.pixelSize: 22
wrapMode: Text.Wrap
color: "#333333"
}
footer: DialogButtonBox {
Button {
text: "确定"
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
onClicked: {
findResultDialog.close()
}
}
}
}
}
关键点:
- 居中显示:使用
(root.width - width) / 2计算居中位置 - 模态对话框:使用
modal: true实现模态对话框 - 自定义样式:使用
background自定义对话框背景 - 按钮角色:使用
DialogButtonBox.buttonRole设置按钮角色
11. ⚠️ 关键配置:deviceTypes 必须包含 “2in1”
这是本文档最重要的发现!
在 entry/src/main/module.json5 文件中,deviceTypes 必须包含 "2in1":
{
"module": {
"name": "entry",
"type": "entry",
"deviceTypes": [
"default",
"tablet",
"2in1" // ⚠️ 必须添加!否则打包会失败
],
// ...
}
}
错误信息:
hvigor ERROR: Failed :entry:default@PackageHap...
Ohos BundleTool [Error]: 10011001 Parse and check args invalid in hap mode.
Error Message: --json-path must be the config.json file or module.json file.
原因分析:
- HarmonyOS PC 设备(如 MateBook)被识别为
"2in1"设备类型 - 如果
deviceTypes中缺少"2in1",打包工具无法正确识别配置文件路径 - 这会导致打包失败,即使应用能在真机上运行
最佳实践:
"deviceTypes": [
"default", // 手机
"tablet", // 平板
"2in1" // ⚠️ PC/2合1设备(必须添加!)
]
🐛 常见问题与解决方案
问题 1:文本样式不生效
症状:勾选样式复选框后,文本样式没有变化。
原因:
TextArea的样式属性没有绑定到全局属性- 属性绑定路径错误
解决方案:
// ✅ 正确:绑定到全局属性
TextArea {
id: textEdit
font.underline: textUnderline // 绑定到 root.textUnderline
font.italic: textItalic
font.bold: textBold
color: textColor
}
// ❌ 错误:没有绑定
TextArea {
id: textEdit
font.underline: false // 硬编码,不会变化
}
问题 2:查找功能找不到文本
症状:输入查找文本后,提示找不到。
原因:
searchText属性没有正确更新- 文本内容为空或格式不匹配
解决方案:
// ✅ 正确:在输入框的 onTextChanged 中更新
TextInput {
id: searchInput
onTextChanged: {
searchText = text // 实时更新
}
}
// ✅ 正确:查找前检查
function findText() {
if (searchText.trim() === "") {
findResultDialog.text = "请输入要查找的文本"
findResultDialog.open()
return
}
// ...
}
问题 3:文本统计不准确
症状:文本统计的字符数、行数、词数不准确。
原因:
- 统计函数没有在文本变化时调用
- 统计算法有误
解决方案:
// ✅ 正确:在 onTextChanged 中更新统计
TextArea {
id: textEdit
onTextChanged: {
updateTextStats() // 文本变化时更新
}
}
// ✅ 正确:使用正确的统计算法
function updateTextStats() {
var text = textEdit.text
textLength = text.length // 字符数
var lines = text.split("\n")
textLineCount = lines.length // 行数
var words = text.trim().split(/\s+/).filter(function(word) { return word.length > 0 })
textWordCount = words.length // 词数
}
问题 4:对话框不显示
症状:调用 dialog.open() 后,对话框不显示。
原因:
- 对话框的
modal属性设置错误 - 对话框的父窗口没有正确设置
解决方案:
// ✅ 正确:设置 modal 和父窗口
Dialog {
id: myDialog
modal: true // 设置为模态对话框
parent: root // 设置父窗口
onOpened: {
console.log("对话框已打开")
}
}
// ✅ 正确:使用 open() 方法
function showDialog() {
myDialog.open()
}
问题 5:TextArea 无法滚动
症状:文本内容超出 TextArea 高度,但无法滚动。
原因:
TextArea没有放在ScrollView中height设置不正确
解决方案:
// ✅ 正确:使用 ScrollView 包裹 TextArea
ScrollView {
anchors.fill: parent
TextArea {
id: textEdit
width: parent.width
height: Math.max(500, implicitHeight) // 使用 implicitHeight
wrapMode: TextArea.Wrap
}
}
// ❌ 错误:直接使用 TextArea,没有 ScrollView
TextArea {
id: textEdit
height: 500 // 固定高度,无法滚动
}
问题 6:打包失败 - json-path 错误
症状:
hvigor ERROR: Failed :entry:default@PackageHap...
Error Message: --json-path must be the config.json file or module.json file.
原因:module.json5 中的 deviceTypes 缺少 "2in1"。
解决方案:
// entry/src/main/module.json5
{
"module": {
"deviceTypes": [
"default",
"tablet",
"2in1" // ⚠️ 必须添加!
]
}
}
💡 最佳实践
1. 文本样式控制
ApplicationWindow {
id: root
// 定义样式属性
property bool textUnderline: false
property bool textItalic: false
property bool textBold: false
// 使用 CheckBox 控制样式
CheckBox {
checked: textUnderline
onCheckedChanged: textUnderline = checked
}
// 绑定到 TextArea
TextArea {
font.underline: textUnderline
font.italic: textItalic
font.bold: textBold
}
}
2. 文本颜色控制
ApplicationWindow {
id: root
// 使用索引映射颜色
property int textColorIndex: 0
property color textColor: {
if (textColorIndex === 0) return "black"
else if (textColorIndex === 1) return "red"
else return "blue"
}
// 使用 RadioButton 控制颜色
RadioButton {
checked: textColorIndex === 0
onCheckedChanged: if (checked) textColorIndex = 0
}
// 绑定到 TextArea
TextArea {
color: textColor
}
}
3. 文本统计
// 实时更新统计
TextArea {
id: textEdit
onTextChanged: {
updateTextStats()
}
}
function updateTextStats() {
var text = textEdit.text
textLength = text.length
textLineCount = text.split("\n").length
textWordCount = text.trim().split(/\s+/).filter(function(word) { return word.length > 0 }).length
}
4. 查找替换
// 查找文本
function findText() {
var index = textEdit.text.indexOf(searchText)
if (index >= 0) {
textEdit.select(index, index + searchText.length)
textEdit.cursorPosition = index + searchText.length
}
}
// 替换文本
function replaceText() {
var text = textEdit.text
if (text.indexOf(searchText) >= 0) {
textEdit.text = text.replace(searchText, replaceText)
}
}
// 全部替换
function replaceAll() {
var text = textEdit.text
while (text.indexOf(searchText) >= 0) {
text = text.replace(searchText, replaceText)
}
textEdit.text = text
}
5. 文本格式化
// 转大写
function toUpperCase() {
textEdit.text = textEdit.text.toUpperCase()
}
// 转小写
function toLowerCase() {
textEdit.text = textEdit.text.toLowerCase()
}
// 首字母大写
function toTitleCase() {
var lines = textEdit.text.split("\n")
var result = []
for (var i = 0; i < lines.length; i++) {
var line = lines[i]
if (line.length > 0) {
line = line.charAt(0).toUpperCase() + line.slice(1).toLowerCase()
}
result.push(line)
}
textEdit.text = result.join("\n")
}
6. 对话框使用
Dialog {
id: myDialog
modal: true
title: "标题"
property string text: ""
contentItem: Text {
text: myDialog.text
wrapMode: Text.Wrap
}
footer: DialogButtonBox {
Button {
text: "确定"
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
onClicked: myDialog.close()
}
}
}
// 使用对话框
function showDialog() {
myDialog.text = "对话框内容"
myDialog.open()
}
📊 项目结构
TextEditorPro/
├── AppScope/
│ └── app.json5 # 应用配置
├── entry/
│ ├── build-profile.json5 # 构建配置
│ ├── src/
│ │ ├── main/
│ │ │ ├── cpp/
│ │ │ │ ├── main.cpp # C++ 入口(qtmain)
│ │ │ │ ├── main.qml # QML UI(多功能文本编辑器)
│ │ │ │ ├── qml.qrc # 资源文件
│ │ │ │ └── CMakeLists.txt
│ │ │ ├── module.json5 # ⚠️ 必须包含 "2in1"
│ │ │ └── ets/ # ArkTS 代码
│ │ └── ohosTest/
│ └── libs/ # Qt 库文件
├── image/
│ └── 运行截图.png # 运行截图
└── build-profile.json5 # 根构建配置
🔧 构建配置要点
CMakeLists.txt
cmake_minimum_required(VERSION 3.5.0)
project(QtForHOSample)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
list(APPEND CMAKE_FIND_ROOT_PATH ${QT_PREFIX})
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS
Concurrent Gui Network Qml Quick QuickControls2
Widgets QuickTemplates2 QmlWorkerScript)
add_library(entry SHARED main.cpp qml.qrc)
target_link_libraries(entry PRIVATE
Qt${QT_VERSION_MAJOR}::Concurrent
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Qml
Qt${QT_VERSION_MAJOR}::Quick
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::QuickControls2
Qt${QT_VERSION_MAJOR}::QuickTemplates2
Qt${QT_VERSION_MAJOR}::QmlWorkerScript
Qt${QT_VERSION_MAJOR}::QOpenHarmonyPlatformIntegrationPlugin # HarmonyOS 插件
)
module.json5(关键配置)
{
"module": {
"name": "entry",
"type": "entry",
"deviceTypes": [
"default",
"tablet",
"2in1" // ⚠️ 必须添加!否则打包会失败
],
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceType": [
"default",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
]
}
}
📚 参考资源
Qt 官方文档
- Qt QML TextArea
- Qt QML TextInput
- Qt QML Dialog
- Qt QML CheckBox
- Qt QML RadioButton
- Qt QML ScrollView
- QSurfaceFormat
HarmonyOS 开发者社区
🎉 总结
通过本项目的开发实践,我们总结了以下关键要点:
- ✅ 必须使用
qtmain()作为入口函数,而不是main() - ✅ OpenGL ES 配置必须在创建应用之前完成
- ✅
deviceTypes必须包含"2in1",否则打包会失败 - ✅ 使用属性绑定实现文本样式控制,通过
CheckBox控制样式属性 - ✅ 使用索引映射实现文本颜色控制,通过
RadioButton控制颜色索引 - ✅ 使用
TextArea实现多行文本编辑,支持样式和颜色绑定 - ✅ 实时更新文本统计,在
onTextChanged中调用统计函数 - ✅ 使用
indexOf和replace实现查找替换,支持单次和全部替换 - ✅ 使用
Dialog组件实现对话框,支持模态和自定义样式 - ✅ 使用
ScrollView包裹TextArea,实现文本滚动 - ✅ 使用 JavaScript 实现文本格式化,支持大小写转换、去除空格等
- ✅ 正确处理文本操作,支持追加行、逐行读取、全部读取、清空等
这些经验对于在 HarmonyOS 平台上开发 Qt 应用至关重要,特别是涉及文本编辑的场景。希望本文档能帮助开发者避免常见陷阱,快速上手 Qt for HarmonyOS 开发,并创建出功能完善的文本编辑器应用。
更多推荐



所有评论(0)