Qt for HarmonyOS 备忘录应用开发实战
本文介绍了一个基于Qt/QML框架开发的HarmonyOS备忘录应用。项目采用MVC架构,前端使用QML实现Material Design风格界面,后端通过C++处理业务逻辑,数据采用JSON文件存储。核心功能包括备忘录的增删改查,实现了清晰的模块划分和数据流设计,确保了良好的用户体验和代码可维护性。该应用具有跨平台能力,性能优异,适合作为日常工具类应用使用。
目录
项目概述

项目地址:https://gitcode.com/szkygc/HarmonyOs_PC-PGC/tree/main/memorandum
项目背景
随着移动应用的快速发展,备忘录应用作为日常工具类应用的重要组成部分,在用户生活中扮演着越来越重要的角色。本项目旨在开发一款基于Qt/QML框架的备忘录应用,运行于HarmonyOS平台,提供简洁、高效、美观的备忘录管理功能。
项目目标
本项目的主要目标包括:
- 功能完整性:实现备忘录的创建、查看、编辑、删除等基本CRUD操作
- 数据持久化:确保备忘录数据能够可靠地保存和恢复
- 用户体验:提供流畅、直观的用户界面,符合现代移动应用的设计规范
- 平台兼容性:适配HarmonyOS平台的特殊要求,确保应用稳定运行
- 代码质量:保持代码结构清晰,便于维护和扩展
功能特性
- ✅ 创建备忘录:支持标题和内容的输入
- ✅ 查看备忘录列表:按更新时间倒序显示所有备忘录
- ✅ 编辑备忘录:点击列表项进入编辑页面进行修改
- ✅ 删除备忘录:支持删除操作,带确认对话框防止误操作
- ✅ 数据持久化:使用JSON文件存储,确保数据不丢失
- ✅ 美观的UI:Material Design风格,现代化界面设计
- ✅ 错误提示:完善的错误处理和用户提示机制
技术栈选择
前端框架:Qt/QML
选择理由:
- 跨平台能力:Qt/QML提供了强大的跨平台支持,可以在HarmonyOS、Android、iOS等多个平台上运行
- 声明式UI:QML的声明式语法使得UI开发更加直观和高效
- 性能优势:Qt的渲染引擎基于OpenGL ES,性能优异
- 丰富的组件库:Qt Quick Controls提供了大量现成的UI组件
- C++集成:可以方便地与C++代码集成,实现复杂业务逻辑
后端语言:C++
选择理由:
- 性能:C++执行效率高,适合处理数据持久化等性能敏感操作
- Qt生态:Qt框架本身就是基于C++的,集成自然
- 数据操作:使用Qt的JSON和文件操作API,代码简洁可靠
数据存储:JSON文件
选择理由:
- 简单可靠:JSON格式易于理解和调试
- 跨平台兼容:不依赖特定数据库驱动
- 轻量级:对于小型应用,文件存储足够高效
- 易于备份:文件可以直接复制备份
UI框架:Qt Quick Controls 2 + Material主题
选择理由:
- Material Design:符合现代移动应用设计规范
- 组件丰富:提供了Button、TextField、Dialog等常用组件
- 主题支持:Material主题提供了统一的视觉风格
- 响应式布局:支持不同屏幕尺寸的自适应
架构设计
整体架构
本项目采用经典的MVC(Model-View-Controller)架构模式,结合Qt/QML的特性,实现了清晰的代码分层:
┌─────────────────────────────────────┐
│ Presentation Layer │
│ (QML UI Components) │
│ - ListView (备忘录列表) │
│ - StackView (页面导航) │
│ - Dialog (对话框) │
└──────────────┬──────────────────────┘
│
│ QML Context Properties
│ Signals & Slots
│
┌──────────────▼──────────────────────┐
│ Business Logic Layer │
│ (C++ MemoStorage) │
│ - loadMemos() │
│ - saveMemo() │
│ - deleteMemo() │
│ - getNextId() │
└──────────────┬──────────────────────┘
│
│ File I/O
│ JSON Operations
│
┌──────────────▼──────────────────────┐
│ Data Layer │
│ (JSON File Storage) │
│ - memos.json │
│ - File System │
└──────────────────────────────────────┘
模块划分
1. 表示层(Presentation Layer)
职责:
- 用户界面展示
- 用户交互处理
- 数据绑定和显示
主要组件:
main.qml:应用主界面,包含列表页和编辑页ListView:备忘录列表展示StackView:页面导航管理Dialog:删除确认对话框
2. 业务逻辑层(Business Logic Layer)
职责:
- 数据业务逻辑处理
- 数据验证
- 与数据层交互
主要类:
MemoStorage:备忘录存储管理类
核心方法:
class MemoStorage : public QObject
{
Q_OBJECT
public:
// 加载所有备忘录
Q_INVOKABLE QVariantList loadMemos();
// 保存备忘录(新建或更新)
Q_INVOKABLE bool saveMemo(int id, const QString &title, const QString &content);
// 删除备忘录
Q_INVOKABLE bool deleteMemo(int id);
// 获取下一个可用ID
Q_INVOKABLE int getNextId();
};
3. 数据层(Data Layer)
职责:
- 数据持久化存储
- 文件读写操作
- JSON数据序列化/反序列化
存储格式:
[
{
"id": 1,
"title": "备忘录标题",
"content": "备忘录内容",
"createdAt": "2025-11-12T09:00:00",
"updatedAt": "2025-11-12T09:30:00"
}
]
数据流设计
读取流程
用户打开应用
↓
QML调用 memoStorage.loadMemos()
↓
MemoStorage::loadJsonArray() 读取JSON文件
↓
解析JSON数组,转换为QVariantList
↓
按更新时间倒序排序
↓
返回给QML
↓
ListView显示数据
保存流程
用户在编辑页点击"保存"
↓
QML验证标题不为空
↓
调用 memoStorage.saveMemo(id, title, content)
↓
MemoStorage加载现有数据
↓
查找是否存在相同ID的记录
↓
存在:更新记录
不存在:创建新记录
↓
更新updatedAt时间戳
↓
保存JSON数组到文件
↓
返回成功/失败
↓
QML刷新列表并返回列表页
删除流程
用户点击删除按钮
↓
显示确认对话框
↓
用户确认删除
↓
调用 memoStorage.deleteMemo(id)
↓
MemoStorage加载现有数据
↓
过滤掉指定ID的记录
↓
保存更新后的JSON数组
↓
返回成功/失败
↓
QML刷新列表
核心功能实现
1. 应用入口与初始化
main.cpp 实现
应用入口文件负责初始化Qt环境、配置OpenGL ES、注册C++类到QML、创建QML引擎等核心工作。
extern "C" int qtmain(int argc, char **argv)
{
// HarmonyOS平台适配
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
// 配置OpenGL ES
QSurfaceFormat format;
format.setRenderableType(QSurfaceFormat::OpenGLES);
format.setVersion(2, 0);
QSurfaceFormat::setDefaultFormat(format);
QGuiApplication app(argc, argv);
// 注册C++类到QML
qmlRegisterType<MemoStorage>("MemoApp", 1, 0, "MemoStorage");
QQmlApplicationEngine engine;
MemoStorage *memoStorage = new MemoStorage(&app);
engine.rootContext()->setContextProperty("memoStorage", memoStorage);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
extern "C" int main(int argc, char **argv)
{
return qtmain(argc, argv);
}
关键点说明:
-
HarmonyOS平台适配:
- 使用
qtmain()作为入口函数,这是HarmonyOS平台的要求 - 配置OpenGL ES表面格式,确保渲染正常
- 设置软件渲染后端作为兜底方案
- 使用
-
C++与QML集成:
- 使用
qmlRegisterType注册C++类型 - 使用
setContextProperty设置全局上下文属性 - 这样QML可以直接访问
memoStorage对象
- 使用
-
错误处理:
- 监听QML警告信号,便于调试
- 提供备用界面加载机制,避免应用完全崩溃
2. 数据存储层实现
MemoStorage类设计
MemoStorage类是整个应用的数据管理核心,负责所有与数据持久化相关的操作。
头文件(memostorage.h):
#ifndef MEMOSTORAGE_H
#define MEMOSTORAGE_H
#include <QObject>
#include <QString>
#include <QVariantMap>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QFile>
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
class MemoStorage : public QObject
{
Q_OBJECT
public:
explicit MemoStorage(QObject *parent = nullptr);
// 加载所有备忘录
Q_INVOKABLE QVariantList loadMemos();
// 保存备忘录(新建或更新)
Q_INVOKABLE bool saveMemo(int id, const QString &title, const QString &content);
// 删除备忘录
Q_INVOKABLE bool deleteMemo(int id);
// 获取下一个可用ID
Q_INVOKABLE int getNextId();
private:
// 获取存储文件路径
QString getStoragePath();
// 确保存储目录存在
void ensureStorageDir();
// 从文件加载JSON数组
QJsonArray loadJsonArray();
// 保存JSON数组到文件
bool saveJsonArray(const QJsonArray &array);
};
#endif // MEMOSTORAGE_H
关键实现代码:
// 获取存储路径
QString MemoStorage::getStoragePath()
{
QString dataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir;
if (!dir.exists(dataPath)) {
dir.mkpath(dataPath);
}
return dataPath + "/memos.json";
}
// 加载备忘录(关键逻辑)
QVariantList MemoStorage::loadMemos()
{
QJsonArray array = loadJsonArray();
QVariantList result;
// 转换为QVariantMap并排序
for (int i = 0; i < array.size(); i++) {
QJsonObject obj = array[i].toObject();
QVariantMap map;
map["id"] = obj["id"].toInt();
map["title"] = obj["title"].toString();
map["content"] = obj["content"].toString();
map["updatedAt"] = obj["updatedAt"].toString();
result.append(map);
}
// 按更新时间倒序排序
std::sort(result.begin(), result.end(), [](const QVariant &a, const QVariant &b) {
return a.toMap()["updatedAt"].toString() > b.toMap()["updatedAt"].toString();
});
return result;
}
// 保存备忘录(关键逻辑)
bool MemoStorage::saveMemo(int id, const QString &title, const QString &content)
{
if (title.trimmed().isEmpty()) return false;
QJsonArray array = loadJsonArray();
QString nowStr = QDateTime::currentDateTime().toString(Qt::ISODate);
// 查找是否存在,存在则更新,不存在则新建
bool found = false;
for (int i = 0; i < array.size(); i++) {
if (array[i].toObject()["id"].toInt() == id) {
QJsonObject obj = array[i].toObject();
obj["title"] = title;
obj["content"] = content;
obj["updatedAt"] = nowStr;
array[i] = obj;
found = true;
break;
}
}
if (!found) {
QJsonObject obj;
obj["id"] = id;
obj["title"] = title;
obj["content"] = content;
obj["createdAt"] = nowStr;
obj["updatedAt"] = nowStr;
array.append(obj);
}
return saveJsonArray(array);
}
设计要点:
- Q_INVOKABLE宏:标记可以在QML中调用的C++方法
- QStandardPaths:使用Qt标准路径API,确保跨平台兼容性
- 错误处理:文件操作都有错误检查和日志输出
- 数据排序:加载时按更新时间倒序排序,最新内容在前
- ID管理:自动递增ID,确保唯一性
3. UI界面实现
主界面结构
应用采用StackView实现页面导航,包含列表页和编辑页两个主要页面。
主窗口结构(main.qml关键部分):
ApplicationWindow {
id: window
width: 480
height: 800
// 主题色定义
readonly property color primaryColor: "#6366f1"
readonly property color backgroundColor: "#f8fafc"
// 备忘录列表模型
ListModel {
id: memoListModel
}
// 加载数据函数
function loadMemos() {
memoListModel.clear()
var memos = memoStorage.loadMemos()
for (var i = 0; i < memos.length; i++) {
memoListModel.append({
memoId: memos[i].id,
title: memos[i].title,
content: memos[i].content,
updatedAt: memos[i].updatedAt
})
}
}
Component.onCompleted: {
loadMemos()
}
// 页面堆栈
StackView {
id: stackView
anchors.fill: parent
initialItem: listPage
}
}
列表页实现
列表页的关键代码:
// 列表项delegate
delegate: Rectangle {
id: memoCard
height: 120
property int memoId: model.memoId
property string memoTitle: model.title
property string memoContent: model.content
// 点击进入编辑页
MouseArea {
anchors.fill: parent
onClicked: {
var page = editPage.createObject(stackView, {
memoId: memoCard.memoId,
memoTitle: memoCard.memoTitle,
memoContent: memoCard.memoContent
})
stackView.push(page)
}
}
// 列表项内容布局
RowLayout {
anchors.fill: parent
ColumnLayout {
Text {
text: memoTitle || "无标题"
font.pixelSize: 28
font.bold: true
}
Text {
text: memoContent || "无内容"
font.pixelSize: 22
maximumLineCount: 2
}
}
Button {
text: "🗑️"
onClicked: deleteConfirmDialog.open()
}
}
}
设计要点:
- Material Design风格:使用紫色主题色(#6366f1),符合Material规范
- 响应式布局:使用ColumnLayout和RowLayout实现自适应布局
- 交互反馈:点击列表项时有视觉反馈效果
- 空状态处理:当没有数据时显示提示信息
- 字体大小优化:标题28px,内容22px,时间18px,确保可读性
编辑页实现
编辑页的关键代码:
Component {
id: editPage
Rectangle {
id: editPageRoot
property int memoId: -1
property string memoTitle: ""
property string memoContent: ""
// 初始化数据
Component.onCompleted: {
titleField.text = memoTitle || ""
contentArea.text = memoContent || ""
}
ColumnLayout {
// 标题栏:返回按钮 + 标题 + 保存按钮
Rectangle {
height: 70
color: primaryColor
// ... 标题栏布局
}
// 编辑区域
ColumnLayout {
// 标题输入
TextField {
id: titleField
font.pixelSize: 32
placeholderText: "标题"
}
// 分割线
Rectangle { height: 2; color: "#e2e8f0" }
// 内容输入(全屏)
ScrollView {
Layout.fillHeight: true
TextArea {
id: contentArea
font.pixelSize: 26
placeholderText: "内容..."
}
}
}
}
// 保存按钮点击处理
Button {
onClicked: {
if (titleField.text.trim() === "") {
errorToast.show("标题不能为空")
return
}
var id = memoId === -1 ? memoStorage.getNextId() : memoId
if (memoStorage.saveMemo(id, titleField.text, contentArea.text)) {
loadMemos()
stackView.pop()
}
}
}
}
}
设计要点:
- 全屏编辑:内容区域占据剩余所有空间,提供良好的编辑体验
- 标题与内容分离:使用分割线清晰区分标题和内容区域
- 数据初始化:在Component.onCompleted中初始化数据,确保编辑时能正确显示
- 输入验证:保存前检查标题是否为空
- 字体大小:标题32px,内容26px,确保输入时清晰可见
数据持久化方案
存储方案选择
在开发过程中,我们经历了多次存储方案的调整:
方案一:SQLite数据库(初始方案)
优点:
- 关系型数据库,支持复杂查询
- 性能优秀,适合大量数据
- 支持事务,数据一致性有保障
缺点:
- 需要SQLite驱动,HarmonyOS平台可能不支持
- 配置复杂,需要额外的CMake配置
- 对于简单应用来说过于重量级
方案二:QML LocalStorage(尝试方案)
优点:
- Qt Quick内置,无需额外驱动
- 纯QML实现,代码简洁
- 跨平台兼容性好
缺点:
- 在HarmonyOS平台上仍然出现驱动加载问题
- 错误信息:“Driver not loaded”
- 无法正常使用
方案三:JSON文件存储(最终方案)
优点:
- 简单可靠,不依赖数据库驱动
- 易于调试,可以直接查看文件内容
- 跨平台兼容性最好
- 轻量级,适合小型应用
- 易于备份和迁移
缺点:
- 不适合大量数据(本项目场景足够)
- 没有事务支持(本项目不需要)
- 查询能力有限(本项目不需要复杂查询)
最终选择: JSON文件存储方案
JSON文件格式设计
[
{
"id": 1,
"title": "会议记录",
"content": "今天下午3点开会讨论项目进度",
"createdAt": "2025-11-12T09:00:00",
"updatedAt": "2025-11-12T09:30:00"
},
{
"id": 2,
"title": "购物清单",
"content": "1. 牛奶\n2. 面包\n3. 鸡蛋",
"createdAt": "2025-11-12T10:00:00",
"updatedAt": "2025-11-12T10:15:00"
}
]
字段说明:
id:唯一标识符,整数类型title:备忘录标题,字符串类型content:备忘录内容,字符串类型createdAt:创建时间,ISO 8601格式字符串updatedAt:更新时间,ISO 8601格式字符串
文件存储位置
使用Qt的 QStandardPaths::AppDataLocation获取应用数据目录:
- HarmonyOS平台:
/data/storage/el2/base/files/memos.json - 其他平台:遵循各平台的标准数据目录规范
数据操作实现
核心数据操作
读取操作:
QVariantList MemoStorage::loadMemos()
{
QJsonArray array = loadJsonArray();
QVariantList result;
// 转换为QVariantMap
for (int i = 0; i < array.size(); i++) {
QJsonObject obj = array[i].toObject();
QVariantMap map;
map["id"] = obj["id"].toInt();
map["title"] = obj["title"].toString();
map["content"] = obj["content"].toString();
map["updatedAt"] = obj["updatedAt"].toString();
result.append(map);
}
// 按更新时间倒序排序
std::sort(result.begin(), result.end(), [](const QVariant &a, const QVariant &b) {
return a.toMap()["updatedAt"].toString() > b.toMap()["updatedAt"].toString();
});
return result;
}
保存操作:
bool MemoStorage::saveMemo(int id, const QString &title, const QString &content)
{
if (title.trimmed().isEmpty()) return false;
QJsonArray array = loadJsonArray();
QString nowStr = QDateTime::currentDateTime().toString(Qt::ISODate);
// 查找并更新或新建
bool found = false;
for (int i = 0; i < array.size(); i++) {
if (array[i].toObject()["id"].toInt() == id) {
// 更新现有记录
QJsonObject obj = array[i].toObject();
obj["title"] = title;
obj["content"] = content;
obj["updatedAt"] = nowStr;
array[i] = obj;
found = true;
break;
}
}
if (!found) {
// 新建记录
QJsonObject obj;
obj["id"] = id;
obj["title"] = title;
obj["content"] = content;
obj["createdAt"] = nowStr;
obj["updatedAt"] = nowStr;
array.append(obj);
}
return saveJsonArray(array);
}
删除操作:
bool MemoStorage::deleteMemo(int id)
{
QJsonArray array = loadJsonArray();
QJsonArray newArray;
// 过滤掉指定ID的记录
for (int i = 0; i < array.size(); i++) {
if (array[i].toObject()["id"].toInt() != id) {
newArray.append(array[i]);
}
}
return saveJsonArray(newArray);
}
UI设计与实现
设计原则
- Material Design规范:遵循Google Material Design设计指南
- 简洁明了:界面简洁,功能清晰
- 易于操作:按钮大小适中,交互反馈明确
- 视觉层次:通过颜色、字体大小、间距建立清晰的视觉层次
- 响应式设计:适配不同屏幕尺寸
颜色方案
// 主题色定义
readonly property color primaryColor: "#6366f1" // 主色调(紫色)
readonly property color backgroundColor: "#f8fafc" // 背景色(浅灰)
readonly property color cardColor: "#ffffff" // 卡片色(白色)
readonly property color textColor: "#1e293b" // 主文本色(深灰)
readonly property color textSecondaryColor: "#64748b" // 次要文本色(中灰)
颜色选择理由:
- 紫色(#6366f1)作为主色调,现代感强,符合Material Design规范
- 浅灰背景(#f8fafc)提供舒适的视觉体验
- 白色卡片(#ffffff)与背景形成对比,突出内容
- 深灰文本(#1e293b)确保良好的可读性
字体大小规范
经过多次优化,最终确定的字体大小:
- 列表页标题:36px
- 列表项标题:28px
- 列表项内容:22px(最多显示2行)
- 列表项时间:18px
- 编辑页标题栏:28px
- 标题输入框:32px
- 内容输入框:26px
- 按钮文字:22-28px
这些字体大小确保了在各种设备上都有良好的可读性。
布局设计
列表页布局
┌─────────────────────────────┐
│ 标题栏(紫色,70px高) │
│ 📝 备忘录 ➕ │
├─────────────────────────────┤
│ │
│ 备忘录列表(可滚动) │
│ ┌─────────────────────┐ │
│ │ 标题(28px) │ │
│ │ 内容预览(22px) │ │
│ │ 时间(18px) 🗑️ │ │
│ └─────────────────────┘ │
│ ┌─────────────────────┐ │
│ │ ... │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────┘
编辑页布局
┌─────────────────────────────┐
│ 标题栏(紫色,70px高) │
│ ← 新建备忘录 保存 │
├─────────────────────────────┤
│ 标题输入区域(100px高) │
│ ┌─────────────────────┐ │
│ │ 标题 │ │
│ └─────────────────────┘ │
├─────────────────────────────┤
│ 分割线(2px) │
├─────────────────────────────┤
│ 内容输入区域(全屏) │
│ ┌─────────────────────┐ │
│ │ │ │
│ │ 内容... │ │
│ │ │ │
│ │ (可滚动) │ │
│ └─────────────────────┘ │
└─────────────────────────────┘
交互设计
点击反馈
列表项点击时有视觉反馈:
// 点击效果
Rectangle {
anchors.fill: parent
color: "#f0f0f0"
opacity: mouseArea.pressed ? 0.3 : 0
Behavior on opacity {
NumberAnimation { duration: 100 }
}
}
页面导航
使用StackView实现页面导航,通过 createObject创建页面实例并推入堆栈。
错误提示
使用Toast消息提示错误,3秒后自动消失。
开发过程中遇到的问题与解决方案
问题一:SQLite驱动未加载
问题描述
在HarmonyOS平台上运行时,应用出现以下错误:
QSqlDatabase: QSQLITE driver not loaded
QSqlDatabase: available drivers:
SQLite驱动不可用
应用无法连接数据库,导致数据持久化功能完全不可用。
问题分析
-
根本原因:
- HarmonyOS平台的Qt构建可能没有包含SQLite插件
- SQLite插件路径配置不正确
- Qt SQL模块在HarmonyOS平台上的支持不完整
-
尝试的解决方案:
- 在CMakeLists.txt中添加SQL模块依赖
- 检查SQLite插件路径
- 尝试使用QML LocalStorage API
-
LocalStorage也失败:
- 尝试使用
QtQuick.LocalStorage模块 - 仍然出现"Driver not loaded"错误
- 说明底层SQLite驱动确实不可用
- 尝试使用
最终解决方案
采用JSON文件存储方案:
-
创建MemoStorage类:
- 使用Qt的JSON API进行数据序列化/反序列化
- 使用QFile进行文件读写操作
- 使用QStandardPaths获取应用数据目录
-
实现要点:
// 文件路径获取 QString MemoStorage::getStoragePath() { QString dataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir dir; if (!dir.exists(dataPath)) { dir.mkpath(dataPath); } return dataPath + "/memos.json"; } // JSON数组加载 QJsonArray MemoStorage::loadJsonArray() { QString filePath = getStoragePath(); QFile file(filePath); if (!file.exists()) { return QJsonArray(); } if (!file.open(QIODevice::ReadOnly)) { qWarning() << "无法打开文件:" << filePath; return QJsonArray(); } QByteArray data = file.readAll(); file.close(); QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull() || !doc.isArray()) { return QJsonArray(); } return doc.array(); } -
优势:
- 不依赖任何数据库驱动
- 跨平台兼容性最好
- 易于调试(可以直接查看JSON文件)
- 轻量级,适合小型应用
经验总结
- 平台兼容性:在跨平台开发中,要特别注意平台特定的限制
- 备选方案:始终准备备选方案,当主要方案不可用时能够快速切换
- 简单优先:对于简单应用,文件存储可能比数据库更合适
问题二:QML component关键字语法错误
问题描述
QML文件编译时出现语法错误:
qrc:/main.qml:199:1: Syntax error
错误位置是使用 component关键字定义组件的地方:
component MemoCard: Rectangle {
// ...
}
问题分析
-
根本原因:
component关键字是Qt 6.2+才引入的特性- HarmonyOS平台使用的Qt版本可能是5.x或6.0/6.1
- 这些版本不支持
component关键字
-
Qt版本差异:
- Qt 5.x:不支持
component关键字 - Qt 6.0/6.1:不支持
component关键字 - Qt 6.2+:支持
component关键字
- Qt 5.x:不支持
解决方案
将component改为内联定义:
修改前:
component MemoCard: Rectangle {
property int memoId: -1
property string title: ""
property string content: ""
// ...
}
ListView {
delegate: MemoCard {
memoId: model.memoId
title: model.title
content: model.content
}
}
修改后:
ListView {
delegate: Rectangle {
id: memoCard
property int memoId: model.memoId
property string memoTitle: model.title
property string memoContent: model.content
// 直接在delegate中定义所有内容
ColumnLayout {
anchors.fill: parent
// ...
}
}
}
经验总结
- 版本兼容性:使用新特性前要检查Qt版本支持情况
- 降级方案:准备兼容旧版本的实现方案
- 代码组织:内联定义虽然代码更长,但兼容性更好
问题三:StackView页面参数传递问题
问题描述
点击列表项进入编辑页时,编辑页中的标题和内容字段为空,无法显示已有数据。
问题分析
-
问题原因:
- 使用
StackView.push(component, properties)方式传递参数 - 参数传递时机不对,组件创建时属性还未设置
- QML组件的属性绑定机制导致数据无法正确初始化
- 使用
-
错误代码:
// 错误的做法:直接push component stackView.push(editPage, { memoId: ..., title: ..., content: ... })
解决方案
使用createObject显式创建对象:
// 正确的做法:使用createObject创建页面实例
onClicked: {
var page = editPage.createObject(stackView, {
memoId: memoCard.memoId,
memoTitle: memoCard.memoTitle,
memoContent: memoCard.memoContent
})
stackView.push(page)
}
// 在Component.onCompleted中初始化数据
Component.onCompleted: {
titleField.text = memoTitle || ""
contentArea.text = memoContent || ""
}
经验总结
- 对象创建:使用
createObject可以更精确地控制对象创建和属性设置 - 生命周期:利用
Component.onCompleted确保在组件完全初始化后再设置数据 - 属性命名:使用
memoTitle而不是title,避免与QML内置属性冲突
问题四:HarmonyOS平台入口函数问题
问题描述
应用在HarmonyOS平台上无法正常启动,出现闪退。
问题分析
-
根本原因:
- HarmonyOS平台要求使用
qtmain()而不是main()作为入口函数 - Qt应用作为共享库加载,生命周期由HarmonyOS管理
- 需要特殊的入口函数签名
- HarmonyOS平台要求使用
-
错误代码:
int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); // ... }
解决方案
使用qtmain作为主入口:
extern "C" int qtmain(int argc, char **argv)
{
// HarmonyOS平台要求:使用qtmain作为入口
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
// 配置OpenGL ES
QSurfaceFormat format;
format.setRenderableType(QSurfaceFormat::OpenGLES);
format.setVersion(2, 0);
QSurfaceFormat::setDefaultFormat(format);
QGuiApplication app(argc, argv);
// 注册C++类到QML
qmlRegisterType<MemoStorage>("MemoApp", 1, 0, "MemoStorage");
QQmlApplicationEngine engine;
MemoStorage *memoStorage = new MemoStorage(&app);
engine.rootContext()->setContextProperty("memoStorage", memoStorage);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
// HarmonyOS兼容性入口
extern "C" int main(int argc, char **argv)
{
return qtmain(argc, argv);
}
关键配置:
// OpenGL ES设置
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
// 表面格式配置
QSurfaceFormat format;
format.setRenderableType(QSurfaceFormat::OpenGLES);
format.setVersion(2, 0);
QSurfaceFormat::setDefaultFormat(format);
// 软件渲染兜底
qputenv("QT_QUICK_BACKEND", "software");
qputenv("QSG_RENDER_LOOP", "basic");
经验总结
- 平台特定要求:不同平台可能有特殊的入口函数要求
- 错误处理:提供兜底方案,确保应用在各种环境下都能运行
- 日志输出:添加详细的日志,便于调试和问题定位
问题五:字体大小和UI优化
问题描述
用户反馈字体太小,看不清;UI需要优化。
问题分析
-
字体问题:
- 初始字体大小偏小(14-20px)
- 在移动设备上可读性差
- 需要根据内容重要性调整字体大小
-
UI问题:
- 列表项高度不够,内容显示不完整
- 编辑页布局不够清晰
- 缺少视觉反馈
解决方案
字体大小优化:
- 列表页标题:24px → 36px
- 列表项标题:20px → 28px
- 列表项内容:16px → 22px
- 编辑页标题输入:22px → 32px
- 编辑页内容输入:18px → 26px
UI优化:
// 列表项:增加高度和内边距
delegate: Rectangle {
height: 120 // 从80px增加到120px
anchors.leftMargin: 20
anchors.rightMargin: 20
}
// 编辑页:标题区域独立,分割线加粗,内容全屏
Rectangle {
Layout.preferredHeight: 100 // 标题区域
}
Rectangle { height: 2 } // 分割线
ScrollView { Layout.fillHeight: true } // 内容全屏
// 点击反馈效果
Rectangle {
opacity: mouseArea.pressed ? 0.3 : 0
Behavior on opacity { NumberAnimation { duration: 100 } }
}
经验总结
- 用户体验优先:字体大小要以用户可读性为准
- 迭代优化:根据用户反馈不断调整UI
- 视觉反馈:提供清晰的交互反馈,提升用户体验
性能优化
1. 数据加载优化
延迟加载
应用启动时只加载必要的UI,数据加载在 Component.onCompleted中进行。
数据排序优化
使用C++的 std::sort进行排序,性能优于QML中的JavaScript排序:
std::sort(result.begin(), result.end(), [](const QVariant &a, const QVariant &b) {
return a.toMap()["updatedAt"].toString() > b.toMap()["updatedAt"].toString();
});
2. UI渲染优化
ListView优化
- 使用
spacing属性控制列表项间距,避免重叠 - 使用
clip: true确保超出部分不渲染 - 列表项高度固定,避免动态计算
动画优化
点击反馈动画使用 NumberAnimation,持续时间100ms,确保流畅:
Behavior on opacity {
NumberAnimation { duration: 100 }
}
3. 内存管理
对象生命周期
- MemoStorage对象在main.cpp中创建,父对象为QGuiApplication,确保应用退出时自动释放
- StackView管理的页面在pop时自动销毁,避免内存泄漏
数据缓存
- 列表数据缓存在ListModel中,避免频繁读取文件
- 只在保存或删除后刷新列表,减少IO操作
4. 文件操作优化
批量操作
保存和删除操作都是先加载全部数据,修改后再一次性保存,避免频繁的文件IO。
错误处理
文件操作都有完善的错误处理,避免因文件问题导致应用崩溃。
项目构建与部署
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
memostorage.cpp
memostorage.h
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
)
资源文件配置(qml.qrc)
<?xml version="1.0" encoding="UTF-8"?>
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
构建步骤
-
环境准备:
- 安装Qt for HarmonyOS SDK
- 配置Qt环境变量
- 准备HarmonyOS开发环境
-
编译:
mkdir build cd build cmake .. -DQT_PREFIX=/path/to/qt make -
部署:
- 将生成的
libentry.so文件部署到HarmonyOS设备 - 配置HarmonyOS应用的EntryAbility
- 运行应用
- 将生成的
调试技巧
-
日志输出:
- 使用
qDebug()输出调试信息 - 使用
qWarning()输出警告信息 - 使用
qCritical()输出错误信息
- 使用
-
QML调试:
- 监听
QQmlApplicationEngine::warnings信号 - 检查QML语法错误
- 使用Qt Creator的QML调试器
- 监听
-
性能分析:
- 使用Qt Creator的性能分析工具
- 监控内存使用情况
- 检查文件IO操作频率
总结与展望
项目总结
本项目成功开发了一款基于Qt/QML的备忘录应用,运行于HarmonyOS平台。项目实现了以下目标:
- 功能完整性:实现了备忘录的完整CRUD操作
- 数据持久化:使用JSON文件实现了可靠的数据存储
- 用户体验:提供了美观、流畅的用户界面
- 平台兼容性:成功适配HarmonyOS平台的特殊要求
- 代码质量:代码结构清晰,易于维护和扩展
技术收获
-
Qt/QML开发:
- 掌握了Qt/QML的基本开发流程
- 理解了C++与QML的交互机制
- 学会了使用Qt Quick Controls组件
-
HarmonyOS平台适配:
- 了解了HarmonyOS平台的特殊要求
- 掌握了OpenGL ES配置方法
- 学会了处理平台特定的问题
-
数据持久化:
- 了解了不同存储方案的优缺点
- 掌握了JSON文件操作
- 学会了根据场景选择合适的存储方案
-
问题解决能力:
- 学会了分析问题根源
- 掌握了多种解决方案的对比和选择
- 提升了调试和问题定位能力
项目亮点
- 架构清晰:采用MVC架构,代码分层明确
- 跨平台兼容:使用标准Qt API,易于移植到其他平台
- 用户体验优秀:Material Design风格,交互流畅
- 代码质量高:错误处理完善,代码注释清晰
- 可扩展性强:架构设计便于后续功能扩展
未来改进方向
-
功能增强:
- 添加搜索功能
- 支持分类和标签
- 添加富文本编辑
- 支持图片附件
- 添加数据导出功能
-
性能优化:
- 实现数据分页加载
- 优化大数据量时的性能
- 添加数据缓存机制
-
用户体验:
- 添加暗色主题
- 支持多语言
- 添加手势操作
- 优化动画效果
-
技术升级:
- 考虑迁移到Qt 6.x
- 使用更现代的QML特性
- 优化代码结构
结语
本项目是一个完整的Qt/QML应用开发实践,涵盖了从需求分析、架构设计、功能实现到问题解决的完整开发流程。通过这个项目,我们不仅掌握了Qt/QML开发技术,更重要的是学会了如何在实际项目中解决问题、优化代码、提升用户体验。
希望这篇技术博客能够帮助其他开发者更好地理解Qt/QML开发,特别是HarmonyOS平台上的应用开发。同时也希望读者能够从我们遇到的问题和解决方案中获得启发,在自己的项目中避免类似的问题。
更多推荐


所有评论(0)