1. 为什么选择Qt开发待办事项记录器
作为一个跨平台的C++图形用户界面应用程序框架,Qt在开发桌面端小型工具类应用时具有独特优势。我选择用Qt来实现待办事项记录器主要基于以下几个考量:
首先,Qt的信号槽机制特别适合处理用户界面交互。待办事项应用的核心就是用户与界面元素(按钮、列表、输入框等)的持续互动,通过信号槽可以优雅地解耦界面与业务逻辑。比如当用户点击"添加"按钮时,按钮的clicked()信号会自动触发我们定义的处理槽函数,这种机制比传统的回调函数更加清晰安全。
其次,Qt提供了丰富的UI组件和布局管理器。QListWidget可以直接用来显示待办事项列表,QLineEdit用于输入新事项,QCheckBox可以标记完成状态,这些现成组件大大减少了开发工作量。通过Qt Designer可视化设计工具,我们可以在几分钟内完成界面原型的搭建。
再者,Qt的跨平台特性让应用可以轻松部署到Windows、macOS和Linux系统。对于个人工具类应用来说,这种"一次编写,到处运行"的能力非常宝贵。我们只需要维护一套代码,就能生成各平台的本地化版本。
最后,Qt对本地数据存储的良好支持也很关键。待办事项需要持久化保存,Qt提供了QSettings用于简单的配置存储,也可以使用SQLite数据库通过Qt SQL模块进行更复杂的数据管理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境准备与项目创建
2.1 Qt开发环境配置
推荐使用Qt 5.15 LTS版本,这是目前最稳定的长期支持版。安装时建议选择"Qt Creator"集成开发环境和对应平台的编译工具链:
- Windows: 勾选MSVC 2019 64-bit组件
- macOS: 勾选Clang 64-bit组件
- Linux: 根据发行版选择gcc或clang
安装完成后,建议配置Qt Creator的代码风格:
- 进入"工具→选项→文本编辑器→行为"
- 设置缩进为4个空格(Qt官方风格)
- 勾选"自动插入星号时的缩进"
注意:如果遇到"this application failed to start because no qt platform plugin could be initialized"错误,通常是因为运行环境缺少必要的Qt库。解决方案是将Qt安装目录下plugins/platforms文件夹复制到可执行文件同级目录。
2.2 创建新项目
在Qt Creator中:
- 选择"文件→新建文件或项目"
- 选择"Application→Qt Widgets Application"
- 设置项目名称为"TodoList"
- 在"类信息"页面,基类选择QMainWindow,这将创建一个带菜单栏的主窗口应用
- 取消勾选"创建界面",我们将手动编写UI代码以获得更大灵活性
生成的项目包含以下关键文件:
- main.cpp:应用入口
- mainwindow.h/.cpp:主窗口类定义和实现
- CMakeLists.txt或.pro文件:构建配置
3. 核心功能实现
3.1 界面布局设计
我们采用经典的待办事项应用布局:
- 顶部:输入框和添加按钮
- 中部:待办事项列表
- 底部:统计信息和操作按钮
在MainWindow构造函数中初始化UI:
cpp复制MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 主窗口设置
setWindowTitle("待办事项记录器");
resize(400, 600);
// 中央部件
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
// 主布局(垂直)
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
// 输入区域
QHBoxLayout *inputLayout = new QHBoxLayout();
m_inputEdit = new QLineEdit(this);
m_addButton = new QPushButton("添加", this);
inputLayout->addWidget(m_inputEdit);
inputLayout->addWidget(m_addButton);
// 待办列表
m_todoList = new QListWidget(this);
m_todoList->setSelectionMode(QAbstractItemView::SingleSelection);
// 底部按钮
QHBoxLayout *buttonLayout = new QHBoxLayout();
m_clearButton = new QPushButton("清空已完成", this);
m_statsLabel = new QLabel("总计: 0 | 已完成: 0", this);
buttonLayout->addWidget(m_statsLabel);
buttonLayout->addStretch();
buttonLayout->addWidget(m_clearButton);
// 组合布局
mainLayout->addLayout(inputLayout);
mainLayout->addWidget(m_todoList);
mainLayout->addLayout(buttonLayout);
}
3.2 数据模型设计
虽然QListWidget可以直接存储文本项,但为了更好管理待办事项的状态(完成/未完成),我们创建自定义数据结构:
cpp复制struct TodoItem {
QString description;
bool completed;
QDateTime createTime;
QDateTime completeTime;
TodoItem(const QString &desc)
: description(desc), completed(false),
createTime(QDateTime::currentDateTime()) {}
};
在MainWindow类中添加数据容器:
cpp复制private:
QVector<TodoItem> m_items;
3.3 功能逻辑实现
添加待办事项
连接添加按钮的点击信号到槽函数:
cpp复制connect(m_addButton, &QPushButton::clicked, this, &MainWindow::addTodoItem);
void MainWindow::addTodoItem()
{
QString text = m_inputEdit->text().trimmed();
if(text.isEmpty()) return;
// 添加到数据模型
m_items.append(TodoItem(text));
// 更新UI
QListWidgetItem *item = new QListWidgetItem(text, m_todoList);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked);
m_inputEdit->clear();
updateStats();
}
标记完成状态
监听列表项的变更信号:
cpp复制connect(m_todoList, &QListWidget::itemChanged,
this, &MainWindow::onItemChanged);
void MainWindow::onItemChanged(QListWidgetItem *item)
{
int row = m_todoList->row(item);
if(row >= 0 && row < m_items.size()) {
m_items[row].completed = (item->checkState() == Qt::Checked);
if(m_items[row].completed) {
m_items[row].completeTime = QDateTime::currentDateTime();
item->setForeground(Qt::gray);
} else {
item->setForeground(Qt::black);
}
updateStats();
}
}
清空已完成事项
cpp复制connect(m_clearButton, &QPushButton::clicked,
this, &MainWindow::clearCompleted);
void MainWindow::clearCompleted()
{
for(int i = m_items.size() - 1; i >= 0; --i) {
if(m_items[i].completed) {
delete m_todoList->takeItem(i);
m_items.remove(i);
}
}
updateStats();
}
更新统计信息
cpp复制void MainWindow::updateStats()
{
int total = m_items.size();
int completed = std::count_if(m_items.begin(), m_items.end(),
[](const TodoItem &item) {
return item.completed;
});
m_statsLabel->setText(
QString("总计: %1 | 已完成: %2").arg(total).arg(completed));
}
4. 数据持久化实现
4.1 使用QSettings存储数据
QSettings适合存储简单的键值对数据,我们将每个待办事项序列化为JSON格式存储:
cpp复制void MainWindow::saveTodos()
{
QSettings settings("MyCompany", "TodoList");
QJsonArray todoArray;
for(const auto &item : m_items) {
QJsonObject obj;
obj["description"] = item.description;
obj["completed"] = item.completed;
obj["createTime"] = item.createTime.toString(Qt::ISODate);
if(item.completed)
obj["completeTime"] = item.completeTime.toString(Qt::ISODate);
todoArray.append(obj);
}
settings.setValue("todos", QJsonDocument(todoArray).toJson());
}
void MainWindow::loadTodos()
{
QSettings settings("MyCompany", "TodoList");
QByteArray jsonData = settings.value("todos").toByteArray();
if(jsonData.isEmpty()) return;
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
QJsonArray todoArray = doc.array();
m_items.clear();
m_todoList->clear();
for(const auto &itemVal : todoArray) {
QJsonObject obj = itemVal.toObject();
TodoItem item(obj["description"].toString());
item.completed = obj["completed"].toBool();
item.createTime = QDateTime::fromString(
obj["createTime"].toString(), Qt::ISODate);
if(item.completed) {
item.completeTime = QDateTime::fromString(
obj["completeTime"].toString(), Qt::ISODate);
}
m_items.append(item);
QListWidgetItem *listItem = new QListWidgetItem(
item.description, m_todoList);
listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable);
listItem->setCheckState(item.completed ? Qt::Checked : Qt::Unchecked);
if(item.completed)
listItem->setForeground(Qt::gray);
}
updateStats();
}
在窗口关闭和打开时调用这些方法:
cpp复制// 在MainWindow构造函数中
loadTodos();
// 重写closeEvent
void MainWindow::closeEvent(QCloseEvent *event)
{
saveTodos();
QMainWindow::closeEvent(event);
}
4.2 使用SQLite数据库(备选方案)
对于更复杂的需求,可以使用Qt SQL模块配合SQLite:
cpp复制bool MainWindow::initDatabase()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("todos.db");
if(!db.open()) {
qWarning() << "无法打开数据库:" << db.lastError().text();
return false;
}
QSqlQuery query;
query.exec("CREATE TABLE IF NOT EXISTS todos ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"description TEXT NOT NULL, "
"completed BOOLEAN NOT NULL DEFAULT 0, "
"create_time DATETIME NOT NULL, "
"complete_time DATETIME)");
return true;
}
5. 功能增强与优化
5.1 添加分类标签
扩展TodoItem结构体:
cpp复制struct TodoItem {
// ...原有字段...
QStringList tags;
};
在UI上添加标签输入和显示:
cpp复制// 在添加待办事项时处理标签
QString text = m_inputEdit->text().trimmed();
QStringList parts = text.split("#");
QString description = parts[0].trimmed();
QStringList tags;
for(int i = 1; i < parts.size(); ++i) {
tags << parts[i].trimmed();
}
TodoItem newItem(description);
newItem.tags = tags;
5.2 实现搜索过滤
添加搜索框和过滤功能:
cpp复制connect(m_searchEdit, &QLineEdit::textChanged,
this, &MainWindow::filterTodos);
void MainWindow::filterTodos(const QString &text)
{
for(int i = 0; i < m_todoList->count(); ++i) {
QListWidgetItem *item = m_todoList->item(i);
bool match = item->text().contains(text, Qt::CaseInsensitive) ||
m_items[i].tags.join(" ").contains(text, Qt::CaseInsensitive);
item->setHidden(!match);
}
}
5.3 添加排序功能
cpp复制void MainWindow::sortTodos(int criteria)
{
auto compare = [criteria](const TodoItem &a, const TodoItem &b) {
switch(criteria) {
case 0: // 创建时间
return a.createTime > b.createTime;
case 1: // 完成时间
if(a.completed && b.completed)
return a.completeTime > b.completeTime;
return a.completed && !b.completed;
case 2: // 字母顺序
return a.description < b.description;
default:
return false;
}
};
std::sort(m_items.begin(), m_items.end(), compare);
// 更新UI
m_todoList->clear();
for(const auto &item : m_items) {
QListWidgetItem *listItem = new QListWidgetItem(
item.description, m_todoList);
listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable);
listItem->setCheckState(item.completed ? Qt::Checked : Qt::Unchecked);
if(item.completed)
listItem->setForeground(Qt::gray);
}
}
6. 打包与发布
6.1 Windows平台打包
使用windeployqt工具自动收集依赖:
bash复制windeployqt --release --no-translations --compiler-runtime TodoList.exe
这将复制所有必要的Qt库到可执行文件目录。建议创建一个安装程序:
- 使用NSIS或Inno Setup创建安装脚本
- 包含所有依赖文件和可执行文件
- 可选添加开始菜单快捷方式和桌面图标
6.2 macOS平台打包
创建.app bundle:
bash复制macdeployqt TodoList.app -dmg
这将生成一个独立的磁盘映像文件,用户可以直接拖拽到Applications文件夹。
6.3 Linux平台打包
对于Debian系发行版,可以创建.deb包:
- 创建标准的Debian包目录结构
- 编写control文件定义元数据
- 将可执行文件和依赖库放入适当位置
- 使用dpkg-deb打包
7. 常见问题解决
7.1 中文显示乱码
确保源代码文件使用UTF-8编码,并在main函数中添加:
cpp复制QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
7.2 程序崩溃问题
常见原因包括:
- 访问越界的列表项
- 空指针访问
- 跨线程UI操作
建议:
- 使用Q_ASSERT进行调试断言
- 启用Qt Creator的AddressSanitizer
- 避免在非UI线程操作界面元素
7.3 样式定制
使用Qt样式表(QSS)自定义外观:
cpp复制m_todoList->setStyleSheet(
"QListWidget {"
" background-color: #f8f8f8;"
" border: 1px solid #ddd;"
" border-radius: 4px;"
"}"
"QListWidget::item {"
" padding: 8px;"
" border-bottom: 1px solid #eee;"
"}"
"QListWidget::item:selected {"
" background-color: #e0e0e0;"
"}");
8. 项目扩展思路
8.1 云同步功能
使用Qt Network模块实现与Web服务的同步:
cpp复制void TodoSyncManager::syncWithServer()
{
QNetworkRequest request(QUrl("https://api.example.com/todos"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QJsonArray todoArray;
// 将本地待办事项转换为JSON
QNetworkReply *reply = m_networkManager.post(
request, QJsonDocument(todoArray).toJson());
connect(reply, &QNetworkReply::finished, [=]() {
if(reply->error() == QNetworkReply::NoError) {
QByteArray response = reply->readAll();
// 处理服务器响应
}
reply->deleteLater();
});
}
8.2 多窗口支持
实现主窗口与详情窗口的交互:
cpp复制void MainWindow::showTodoDetails(int index)
{
if(index < 0 || index >= m_items.size()) return;
TodoDetailDialog *dialog = new TodoDetailDialog(m_items[index], this);
connect(dialog, &TodoDetailDialog::todoUpdated,
this, &MainWindow::updateTodoItem);
dialog->show();
}
8.3 快捷键支持
为常用操作添加快捷键:
cpp复制// 在MainWindow构造函数中
m_addAction = new QAction("添加", this);
m_addAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return));
connect(m_addAction, &QAction::triggered, this, &MainWindow::addTodoItem);
addAction(m_addAction);
9. 性能优化建议
9.1 大数据量优化
当待办事项数量很大时(超过1000条),考虑:
- 使用QTableView + QStandardItemModel代替QListWidget
- 实现自定义委托只渲染可见项
- 添加分页或虚拟滚动功能
9.2 减少UI阻塞
对于耗时操作(如加载大量数据),使用QProgressDialog提供反馈:
cpp复制QProgressDialog progress("正在加载...", "取消", 0, m_items.size(), this);
progress.setWindowModality(Qt::WindowModal);
for(int i = 0; i < m_items.size(); ++i) {
progress.setValue(i);
if(progress.wasCanceled()) break;
// 加载单个项
qApp->processEvents(); // 保持UI响应
}
9.3 内存管理
- 使用智能指针管理动态分配的对象
- 及时断开不再需要的信号槽连接
- 对于大型数据结构,考虑使用数据库后端而非内存存储
10. 测试与调试技巧
10.1 单元测试
使用Qt Test框架编写测试用例:
cpp复制class TestTodoList : public QObject
{
Q_OBJECT
private slots:
void testAddTodo()
{
MainWindow window;
window.addTodoItem("Test item");
QCOMPARE(window.todoCount(), 1);
}
};
QTEST_MAIN(TestTodoList)
#include "test_todolist.moc"
10.2 界面测试
使用Qt Test的GUI测试功能:
cpp复制void TestTodoList::testAddButton()
{
MainWindow window;
QTest::mouseClick(window.addButton(), Qt::LeftButton);
// 验证添加行为
}
10.3 调试输出
使用qDebug()输出调试信息:
cpp复制qDebug() << "当前待办事项数量:" << m_items.size();
在pro文件中添加配置启用详细调试信息:
code复制CONFIG += console debug
11. 代码组织与架构优化
11.1 分离数据模型与界面
创建独立的TodoModel类继承QAbstractListModel:
cpp复制class TodoModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
DescriptionRole = Qt::UserRole + 1,
CompletedRole,
CreateTimeRole,
TagsRole
};
// 实现必要的虚函数...
};
11.2 使用MVC模式
将视图与模型分离:
cpp复制// MainWindow中
m_todoModel = new TodoModel(this);
m_todoView = new QListView(this);
m_todoView->setModel(m_todoModel);
11.3 命令模式实现撤销/重做
创建命令基类和具体命令类:
cpp复制class TodoCommand : public QUndoCommand
{
public:
virtual void undo() override = 0;
virtual void redo() override = 0;
};
class AddTodoCommand : public TodoCommand
{
public:
AddTodoCommand(TodoModel *model, const QString &text)
: m_model(model), m_text(text) {}
void redo() override { m_index = m_model->addTodo(m_text); }
void undo() override { m_model->removeTodo(m_index); }
private:
TodoModel *m_model;
QString m_text;
int m_index;
};
12. 跨平台注意事项
12.1 文件路径处理
使用Qt的路径处理函数确保跨平台兼容性:
cpp复制QString dataPath = QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation);
QDir().mkpath(dataPath);
QString dbPath = QDir(dataPath).filePath("todos.db");
12.2 平台特定样式
检测当前平台并应用适当样式:
cpp复制#ifdef Q_OS_WIN
setStyle(QStyleFactory::create("WindowsVista"));
#elif defined(Q_OS_MAC)
setStyle(QStyleFactory::create("Macintosh"));
#else
setStyle(QStyleFactory::create("Fusion"));
#endif
12.3 高DPI支持
启用高DPI缩放:
cpp复制// 在main函数中
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
13. 国际化支持
13.1 多语言翻译
使用Qt Linguist工具流程:
- 在代码中使用tr()标记所有用户可见字符串
- 运行lupdate生成.ts文件
- 使用Linguist翻译.ts文件
- 运行lrelease生成.qm二进制翻译文件
- 在应用中加载翻译文件:
cpp复制QTranslator translator;
translator.load("todolist_zh.qm", ":/translations");
app.installTranslator(&translator);
13.2 本地化格式
使用QLocale处理日期、时间等本地化格式:
cpp复制QLocale locale = QLocale::system();
QString dateStr = locale.toString(QDate::currentDate(), QLocale::ShortFormat);
14. 插件系统设计
14.1 插件接口定义
创建抽象插件接口:
cpp复制class TodoPluginInterface
{
public:
virtual ~TodoPluginInterface() = default;
virtual QString name() const = 0;
virtual void initialize(MainWindow *window) = 0;
};
Q_DECLARE_INTERFACE(TodoPluginInterface, "com.example.TodoPluginInterface/1.0")
14.2 插件加载机制
扫描插件目录并加载:
cpp复制void MainWindow::loadPlugins()
{
QDir pluginsDir(qApp->applicationDirPath() + "/plugins");
foreach(QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = loader.instance();
if(plugin) {
TodoPluginInterface *todoPlugin =
qobject_cast<TodoPluginInterface *>(plugin);
if(todoPlugin) {
todoPlugin->initialize(this);
m_plugins.append(todoPlugin);
}
}
}
}
15. 持续集成与自动化测试
15.1 CI配置示例
使用GitHub Actions的示例配置:
yaml复制name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: '5.15.2'
- name: Configure
run: qmake
- name: Build
run: make
- name: Test
run: ./todolist_test
15.2 自动化打包
编写脚本自动生成各平台发布包:
bash复制#!/bin/bash
# Linux打包脚本
qmake && make clean && make
linuxdeployqt ./TodoList -appimage
16. 用户反馈与错误报告
16.1 实现反馈对话框
cpp复制void MainWindow::showFeedbackDialog()
{
QDialog dialog(this);
QVBoxLayout layout(&dialog);
QTextEdit *feedbackEdit = new QTextEdit(&dialog);
QPushButton *sendButton = new QPushButton("发送反馈", &dialog);
layout.addWidget(new QLabel("请描述您的问题或建议:"));
layout.addWidget(feedbackEdit);
layout.addWidget(sendButton);
connect(sendButton, &QPushButton::clicked, [&]() {
sendFeedback(feedbackEdit->toPlainText());
dialog.accept();
});
dialog.exec();
}
16.2 错误收集系统
使用Qt Network上传错误报告:
cpp复制void ErrorHandler::uploadCrashReport(const QByteArray &reportData)
{
QNetworkRequest request(QUrl("https://api.example.com/crash-reports"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QJsonObject report;
report["version"] = qApp->applicationVersion();
report["platform"] = QSysInfo::productType();
report["stacktrace"] = QString::fromUtf8(reportData);
m_networkManager.post(request, QJsonDocument(report).toJson());
}
17. 可访问性支持
17.1 屏幕阅读器兼容
为UI元素设置可访问属性:
cpp复制m_addButton->setAccessibleName("添加待办事项按钮");
m_addButton->setAccessibleDescription("点击此按钮将当前输入框中的内容添加为新的待办事项");
17.2 键盘导航支持
确保所有功能可通过键盘操作:
cpp复制// 设置tab顺序
setTabOrder(m_inputEdit, m_addButton);
setTabOrder(m_addButton, m_todoList);
setTabOrder(m_todoList, m_clearButton);
18. 安全注意事项
18.1 输入验证
防止潜在的安全问题:
cpp复制void MainWindow::addTodoItem()
{
QString text = m_inputEdit->text().trimmed();
// 防止HTML/JS注入
text = text.toHtmlEscaped();
// 限制长度
if(text.length() > 500) {
QMessageBox::warning(this, "输入过长", "待办事项不能超过500个字符");
return;
}
// ...其余逻辑...
}
18.2 数据加密
敏感数据加密存储:
cpp复制QByteArray encryptData(const QByteArray &data, const QByteArray &key)
{
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC);
QByteArray iv(16, 0); // 初始化向量
return encryption.encode(data, key, iv);
}
19. 性能监控与分析
19.1 性能统计
使用QElapsedTimer测量关键操作耗时:
cpp复制QElapsedTimer timer;
timer.start();
// 执行待测操作
loadTodos();
qDebug() << "加载待办事项耗时:" << timer.elapsed() << "毫秒";
19.2 内存分析
使用Qt自带的内存分析工具:
cpp复制#include <QDebug>
#include <QMemoryInfo>
void printMemoryUsage()
{
QMemoryInfo memory;
qDebug() << "内存使用:"
<< "总物理内存:" << memory.totalPhysical()/1024/1024 << "MB,"
<< "可用内存:" << memory.availablePhysical()/1024/1024 << "MB,"
<< "进程占用:" << memory.workingSet()/1024 << "KB";
}
20. 项目文档编写
20.1 使用Doxygen生成API文档
示例注释:
cpp复制/**
* @class MainWindow
* @brief 待办事项记录器的主窗口类
*
* 这个类负责管理应用程序的主界面和核心功能,
* 包括待办事项的添加、删除、标记完成等操作。
*/
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
/**
* @brief 构造函数
* @param parent 父窗口指针
*/
explicit MainWindow(QWidget *parent = nullptr);
/**
* @brief 添加新的待办事项
* @param text 待办事项描述文本
* @return 添加成功返回true,否则返回false
*/
bool addTodoItem(const QString &text);
};
20.2 用户手册编写
使用Qt帮助系统:
- 编写.qhp帮助项目文件
- 使用qhelpgenerator生成.qch二进制帮助文件
- 在应用中集成帮助系统:
cpp复制QHelpEngine *helpEngine = new QHelpEngine(":/docs/todolist.qhc", this);
helpEngine->setupData();
QHelpContentWidget *contentWidget = helpEngine->contentWidget();
21. 第三方库集成
21.1 使用Qt Charts添加统计图表
示例代码:
cpp复制#include <QtCharts>
void MainWindow::showStatsChart()
{
QChart *chart = new QChart();
QPieSeries *series = new QPieSeries();
series->append("已完成", completedCount());
series->append("未完成", totalCount() - completedCount());
chart->addSeries(series);
chart->setTitle("待办事项完成情况");
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
QDialog *dialog = new QDialog(this);
QVBoxLayout *layout = new QVBoxLayout(dialog);
layout->addWidget(chartView);
dialog->resize(400, 300);
dialog->exec();
}
21.2 集成SQLite数据库
更复杂的数据存储方案:
cpp复制bool TodoDatabase::initialize()
{
m_db = QSqlDatabase::addDatabase("QSQLITE");
m_db.setDatabaseName("todos.db");
if(!m_db.open()) {
qWarning() << "数据库打开失败:" << m_db.lastError().text();
return false;
}
QSqlQuery query;
if(!query.exec("CREATE TABLE IF NOT EXISTS todos ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"description TEXT NOT NULL, "
"completed BOOLEAN NOT NULL DEFAULT 0, "
"created_at DATETIME NOT NULL, "
"completed_at DATETIME, "
"tags TEXT)")) {
qWarning() << "创建表失败:" << query.lastError().text();
return false;
}
return true;
}
22. 现代Qt特性应用
22.1 使用QML重写界面
创建混合Qt Widgets/QML应用:
qml复制// TodoItem.qml
import QtQuick 2.15
Rectangle {
property string description
property bool completed
width: parent.width
height: 50
Text {
text: description
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
color: completed ? "gray" : "black"
}
CheckBox {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 10
checked: completed
}
}
在C++中集成:
cpp复制QQuickView *view = new QQuickView();
view->setSource(QUrl("qrc:/main.qml"));
QWidget *container = QWidget::createWindowContainer(view, this);
22.2 使用Model/View框架
创建自定义模型:
cpp复制class TodoModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
DescriptionRole = Qt::UserRole + 1,
CompletedRole,
TagsRole
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
// 自定义方法
void addTodo(const QString &description);
void removeTodo(int index);
void toggleCompleted(int index);
private:
QVector<TodoItem> m_items;
};
23. 多线程处理
23.1 后台数据加载
使用QRunnable和QThreadPool:
cpp复制class DataLoader : public QRunnable
{
public:
DataLoader(TodoModel *model) : m_model(model) {}
void run() override {
// 模拟耗时加载
QThread::sleep(2);
QVector<TodoItem> items = loadFromDatabase();
// 通过信号传递回主线程
emit m_model->dataLoaded(items);
}
private:
TodoModel *m_model;
};
// 在模型中
void TodoModel::loadAsync()
{
DataLoader *loader = new DataLoader(this);
connect(this, &TodoModel::dataLoaded,
this, &TodoModel::handleDataLoaded);
QThreadPool::globalInstance()->start(loader);
}
23.2 线程安全数据访问
使用QMutex保护共享数据:
cpp复制class ThreadSafeTodoList
{
public:
void addItem(const TodoItem &item) {
QMutexLocker locker(&m_mutex);
m_items.append(item);
}
QVector<TodoItem> items() const {
QMutexLocker locker(&m_mutex);
return m_items;
}
private:
mutable QMutex m_mutex;
QVector<TodoItem> m_items;
};
24. 动画与视觉效果
24.1 添加项目动画
使用QPropertyAnimation:
cpp复制void MainWindow::animateAdd(int index)
{
QListWidgetItem *item = m_todoList->item(index);
QWidget *widget = m_todoList->itemWidget(item);
widget->setGraphicsEffect(new QGraphicsOpacityEffect(widget));
QPropertyAnimation *anim = new QPropertyAnimation(
widget->graphicsEffect(), "opacity");
anim->setDuration(500);
anim->setStartValue(0);
anim->setEndValue(1);
anim->setEasingCurve(QEasingCurve::InOutQuad);
anim->start(QAbstractAnimation::DeleteWhenStopped);
}
24.2 主题切换支持
实现动态主题切换:
cpp复制void MainWindow::setDarkTheme(bool dark)
{
if(dark) {
qApp->setStyleSheet(
"QWidget {"
" background-color: #333;"
" color: #eee;"
"}"
"QLineEdit {"
" background: #444;"
" border: 1px solid #555;"
"}");
} else {
qApp->setStyleSheet("");
}
}
25. 实际开发中的经验分享
在开发这个待办事项记录器的过程中,我积累了一些值得分享的经验:
-
信号槽连接的注意事项:
- 避免循环触发(如itemChanged触发数据修改又触发itemChanged)
- 及时断开不再需要的连接(如对话框关闭时)
- 优先使用新式connect语法(编译时检查)
-
内存泄漏排查:
- 使用QObject的父子关系自动管理内存
- 对于非QObject派生类,使用
