Qt项目锻炼——TODO清单(二)

发布于:2025-07-06 ⋅ 阅读:(13) ⋅ 点赞:(0)

Qt里有很多类和函数我也不知道,只能利用好Qt Assistant当场查找。
在这里插入图片描述

实现 TaskModel 类

TaskModel 类继承自 QSqlTableModel,用于处理与数据库中任务表的交互,以及对任务数据的显示和操作。

头文件

class TaskModel : public QSqlTableModel 
{  
    Q_OBJECT
public:  
    // 列索引枚举类型,是模型中表的列名
    enum Columns
    {
       IdColumn = 0,  
       TitleColumn,  
       DescriptionColumn,  
       PriorityColumn,  
       DueDateColumn,  
       CompletedColumn,  
       CreatedAtColumn  
   };  

   explicit TaskModel(QObject* parent = nullptr);  

   // 自定义数据显示  
   QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;  
   bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;  

   // 任务操作  
   bool addTask(const Task& task);  
   bool updateTask(const Task& task);  
   bool deleteTask(int taskId);  
   QList<Task> getAllTasks() const;  

   // 过滤方法  
   void setFilterByStatus(bool showCompleted, bool showOverdue);  
   void setSearchText(const QString& text);  
};

源文件


TaskModel::TaskModel(QObject* parent) : QSqlTableModel(parent)
{
    setTable("tasks");
    setEditStrategy(QSqlTableModel::OnManualSubmit);
    select(); //更新模型里的数据
}

QVariant TaskModel::data(const QModelIndex& index, int role) const
{
    if (role == Qt::DisplayRole)
    {
        if (index.column() == PriorityColumn)
        {
            int priority = QSqlTableModel::data(index, role).toInt();
            switch (priority)
            {
            case Task::Low: return "Low";
            case Task::Medium: return "Medium";
            case Task::High: return "High";
            default: return "";
            }
        }
    }
    return QSqlTableModel::data(index, role);
}

bool TaskModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    return QSqlTableModel::setData(index, value, role);
}

bool TaskModel::addTask(const Task& task)
{
    QSqlQuery query; // Ensure QSqlQuery is fully included and initialized
    // Prepare the SQL query to insert a new task
    query.prepare("INSERT INTO tasks (title, description, priority, due_date, completed) "
        "VALUES (:title, :description, :priority, :due_date, :completed)");
    // Bind the values to the query parameters
    query.bindValue(":title", task.title());
    query.bindValue(":description", task.description());
    query.bindValue(":priority", task.priority());
    query.bindValue(":due_date", task.dueDate().toString("yyyy-MM-dd"));
    query.bindValue(":completed", task.completed());

    if (query.exec())
    {
        select();
        return true;
    }
    else
    {
        qDebug() << "Failed to add task:" << query.lastError().text();
        return false;
    }
}

bool TaskModel::updateTask(const Task& task)
{
    QSqlQuery query; // Ensure QSqlQuery is fully included and initialized
    query.prepare("UPDATE tasks SET title = :title, description = :description, "
        "priority = :priority, due_date = :due_date, completed = :completed "
        "WHERE id = :id");
    query.bindValue(":id", task.id());
    query.bindValue(":title", task.title());
    query.bindValue(":description", task.description());
    query.bindValue(":priority", task.priority());
    query.bindValue(":due_date", task.dueDate().toString("yyyy-MM-dd"));
    query.bindValue(":completed", task.completed());

    if (query.exec())
    {
        select();
        return true;
    }
    else
    {
        qDebug() << "Failed to update task:" << query.lastError().text();
        return false;
    }
}

bool TaskModel::deleteTask(int taskId)
{
    QSqlQuery query; // Ensure QSqlQuery is fully included and initialized
    query.prepare("DELETE FROM tasks WHERE id = :id");
    query.bindValue(":id", taskId);

    if (query.exec())
    {
        select();
        return true;
    }
    else
    {
        qDebug() << "Failed to delete task:" << query.lastError().text();
        return false;
    }
}

QList<Task> TaskModel::getAllTasks() const
{
    QList<Task> tasks;
    for (int i = 0; i < rowCount(); ++i)
    {
        int id = data(index(i, IdColumn)).toInt();
        QString title = data(index(i, TitleColumn)).toString();
        QString description = data(index(i, DescriptionColumn)).toString();
        int priority = data(index(i, PriorityColumn)).toInt();
        QDate dueDate = QDate::fromString(data(index(i, DueDateColumn)).toString(), "yyyy-MM-dd");
        bool completed = data(index(i, CompletedColumn)).toBool();

        tasks.append(Task(id, title, description, static_cast<Task::Priority>(priority), dueDate, completed));
    }
    return tasks;
}

void TaskModel::setFilterByStatus(bool showCompleted, bool showOverdue)
{
    QString filter;
    if (!showCompleted)
    {
        filter += "completed = 0";
    }
    if (showOverdue)
    {
        if (!filter.isEmpty())
        {
            filter += " AND ";
        }
        filter += "due_date < CURDATE() AND completed = 0";
    }
    setFilter(filter);
    select();
}

void TaskModel::setSearchText(const QString& text)
{
    QString filter = QString("title LIKE '%%1%' OR description LIKE '%%1%'").arg(text);
    setFilter(filter);
    select();
}


补充

QSqlTableModel 是 Qt 框架中用于数据库操作的模型类,它继承自 QAbstractTableModel,专为表格数据与 SQL 数据库之间的交互设计。以下是它与普通 QAbstractTableModel(或其子类)的主要区别及使用方法:

QSqlTableModel介绍

  1. 数据来源

    • QSqlTableModel:直接连接数据库表,数据自动从 SQL 查询获取。
    • 普通 TableModel:需要手动管理内存中的数据(如 QListQVector)。
  2. 数据操作

    • QSqlTableModel:支持数据库级的增删改查(CRUD),通过 insertRow()setData()removeRow() 等方法自动生成 SQL 语句。
    • 普通 TableModel:需手动实现数据操作逻辑,如在内存中添加/删除项,并手动触发数据变更信号(如 dataChanged())。
  3. 性能

    • QSqlTableModel:适合中小型数据集,大数据量时可能因频繁数据库查询导致性能下降。
    • 普通 TableModel:适合完全在内存中处理的数据,性能取决于实现方式。
  4. 同步机制

    • QSqlTableModel:支持自动或手动提交更改到数据库(通过 submitAll()revertAll())。
    • 普通 TableModel:无内置同步机制,需自行实现与外部数据源的同步。

QSqlTableModel 使用方法

1. 基本初始化
#include <QSqlTableModel>
#include <QTableView>

// 创建模型并连接到数据库表
QSqlTableModel *model = new QSqlTableModel(this);
model->setTable("tasks");  // 指定数据库表名
model->setEditStrategy(QSqlTableModel::OnManualSubmit);  // 设置编辑策略
model->select();  // 执行 SQL 查询,加载数据

// 将模型应用到视图
QTableView *view = new QTableView;
view->setModel(model);
view->show();
2. 编辑策略

控制数据何时提交到数据库:

  • QSqlTableModel::OnFieldChange:每次单元格修改立即提交。
  • QSqlTableModel::OnRowChange:切换行时提交当前行的修改。
  • QSqlTableModel::OnManualSubmit:手动调用 submitAll() 提交所有更改。
3. 数据操作
// 添加新行
model->insertRow(model->rowCount());
model->setData(model->index(model->rowCount()-1, 0), "New Task");  // 设置数据
model->submitAll();  // 提交更改到数据库

// 修改数据
model->setData(model->index(0, 1), "Updated Description");
model->submitAll();

// 删除选中行
QModelIndexList selected = view->selectionModel()->selectedRows();
for (int i = 0; i < selected.size(); ++i) {
    model->removeRow(selected.at(i).row());
}
model->submitAll();
4. 过滤与排序
// 过滤数据(相当于 SQL WHERE 子句)
model->setFilter("completed = 1");  // 只显示已完成的任务
model->select();

// 排序数据(相当于 SQL ORDER BY)
model->setSort(2, Qt::AscendingOrder);  // 按第3列升序排序
model->select();
5. 高级用法
  • 自定义数据显示:重写 data() 方法,自定义单元格显示格式。
  • 关联多表:使用 QSqlRelationalTableModel 处理外键关联。
  • 事务处理:结合 QSqlDatabase::transaction()submitAll() 确保数据一致性。

何时选择 QSqlTableModel?

  • 当数据主要存储在数据库中,且需要直接操作数据库表时。
  • 当需要快速实现数据库与界面的双向同步时。
  • 当业务逻辑简单,无需复杂的数据处理时。

何时选择普通 TableModel?

  • 当数据来源非数据库(如网络、文件)时。
  • 当需要复杂的数据处理或自定义逻辑时。
  • 当性能是关键因素,需要优化内存使用时。

总结

QSqlTableModel 是数据库操作的便捷工具,适合快速开发数据库应用;而普通 TableModel 提供更高的灵活性,适合复杂场景。根据项目需求选择合适的模型,可大幅提高开发效率。

在 Qt 的模型/视图(Model/View)架构中,QAbstractItemModel::data()核心虚函数,负责为视图提供指定索引(QModelIndex)和角色(Qt::ItemDataRole)的数据。它是模型与视图通信的桥梁,决定了数据如何在界面上显示和交互。

data函数

一、函数原型与参数

virtual QVariant QAbstractItemModel::data(const QModelIndex &index, int role = Qt::DisplayRole) const;
  • 参数
    • index:指定数据项的位置(行、列、父项)。
    • role:指定数据的用途(如显示文本、编辑文本、图标等)。
  • 返回值
    • QVariant:存储任意类型的数据(需与角色类型匹配),无效数据返回 QVariant()

二、核心作用

  1. 数据复用:同一数据项可根据不同角色提供不同形式的数据。
    例如:

    • DisplayRole:显示为 "$100.00"(字符串)。
    • UserRole:存储为 100.0(浮点数,用于计算)。
  2. 视图渲染控制:视图根据角色获取数据并渲染。
    例如:

    • DecorationRoleQIcon 显示在项前。
    • FontRoleQFont 决定文本字体。
  3. 交互逻辑分离

    • 编辑操作(如 QLineEdit)使用 EditRole 的数据,而非 DisplayRole,避免格式干扰。

三、常用角色与返回值类型

角色 用途 常见返回类型
Qt::DisplayRole 显示文本 QString
Qt::EditRole 编辑文本 QString
Qt::DecorationRole 图标/图像 QIcon, QPixmap, QColor
Qt::ToolTipRole 鼠标悬停提示 QString
Qt::FontRole 字体 QFont
Qt::TextAlignmentRole 文本对齐方式 Qt::AlignmentFlag
Qt::BackgroundColorRole 背景颜色 QBrush
Qt::ForegroundRole 前景颜色(文本颜色) QBrush
Qt::CheckStateRole 复选框状态 Qt::CheckState
Qt::UserRole 用户自定义数据 任意类型(如 int, QDate

四、实现示例

1. 简单表格模型(QAbstractTableModel 子类)
class MyTableModel : public QAbstractTableModel {
public:
    // 必须实现的三个虚函数
    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        return dataList.size();
    }
    
    int columnCount(const QModelIndex &parent = QModelIndex()) const override {
        return 3; // 3列
    }
    
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (!index.isValid() || index.row() >= dataList.size())
            return QVariant();
        
        const auto &item = dataList[index.row()];
        
        switch (role) {
            case Qt::DisplayRole:
            case Qt::EditRole:
                switch (index.column()) {
                    case 0: return item.name;      // 第1列:名称
                    case 1: return item.age;       // 第2列:年龄
                    case 2: return item.score;     // 第3列:分数
                    default: return QVariant();
                }
            
            case Qt::ForegroundRole:
                if (item.score < 60)
                    return QBrush(Qt::red);      // 分数不及格:红色文本
                return QBrush(Qt::black);
            
            case Qt::ToolTipRole:
                return QString("Name: %1\nAge: %2\nScore: %3")
                       .arg(item.name).arg(item.age).arg(item.score);
            
            case Qt::UserRole:
                return QVariant::fromValue(item); // 存储完整数据对象
            
            default:
                return QVariant();
        }
    }
    
private:
    struct Item {
        QString name;
        int age;
        double score;
    };
    QList<Item> dataList;
};
2. 树形模型(QAbstractItemModel 子类)
QVariant MyTreeModel::data(const QModelIndex &index, int role) const {
    if (!index.isValid())
        return QVariant();
    
    TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
    
    switch (role) {
        case Qt::DisplayRole:
            return item->data(index.column()); // 返回节点数据
        
        case Qt::DecorationRole:
            if (index.column() == 0) { // 第1列显示图标
                if (item->hasChildren())
                    return folderIcon;
                else
                    return fileIcon;
            }
            return QVariant();
        
        case Qt::CheckStateRole:
            if (index.column() == 0) { // 第1列支持复选框
                return item->isChecked() ? Qt::Checked : Qt::Unchecked;
            }
            return QVariant();
        
        default:
            return QVariant();
    }
}

五、使用技巧

  1. 角色组合

    • 同一索引可针对不同角色返回不同数据。例如:
      if (role == Qt::DisplayRole)
          return QString::number(value);
      else if (role == Qt::EditRole)
          return value; // 编辑时返回原始数值,而非字符串
      
  2. 自定义角色

    • 使用 Qt::UserRole + n 定义自定义角色,存储特殊数据(如对象指针、ID)。
      enum CustomRoles {
          SortRole = Qt::UserRole + 1, // 用于排序的数值
          IdRole = Qt::UserRole + 2    // 存储唯一ID
      };
      
      // 在data()中实现
      case SortRole: return item.sortValue;
      case IdRole: return item.id;
      
  3. 性能优化

    • 避免在 data() 中执行耗时操作(如文件读取、网络请求)。
    • 缓存频繁访问的数据,减少重复计算。

六、常见问题

  1. 视图不显示数据

    • 检查 data() 是否为 Qt::DisplayRole 返回了有效数据。
    • 确保 rowCount()columnCount() 返回正确值。
  2. 编辑异常

    • 若编辑框显示异常,检查 EditRole 是否与 DisplayRole 兼容。
  3. 图标/颜色不生效

    • 确保 DecorationRole 返回 QIconQPixmapForegroundRole 返回 QBrush

总结

data() 函数是 Qt 模型的核心,通过角色机制实现了数据的多维度表达。理解并正确实现它,是开发灵活、高效 Qt 应用的关键。

QSqlQuery 是 Qt 框架中用于执行 SQL 语句和处理查询结果的核心类。它支持多种数据库操作,包括查询、插入、更新和删除等。以下是 QSqlQuery 的详细用法和最佳实践:

QSqlQuery

一、基本使用流程

1. 初始化与执行 SQL
// 假设已创建并打开数据库连接(如使用 QSqlDatabase::addDatabase())
QSqlQuery query;  // 默认使用默认数据库连接

// 执行简单查询
bool success = query.exec("SELECT * FROM users");
if (!success) {
    qDebug() << "Query error:" << query.lastError().text();
}
2. 遍历查询结果
while (query.next()) {  // 逐行移动光标
    QString name = query.value(0).toString();  // 第1列数据(索引0)
    int age = query.value(1).toInt();          // 第2列数据
    qDebug() << "Name:" << name << "Age:" << age;
}
3. 使用参数化查询(防止 SQL 注入)
// 方式1:命名参数
query.prepare("INSERT INTO users (name, age) VALUES (:name, :age)");
query.bindValue(":name", "Alice");
query.bindValue(":age", 30);
query.exec();

// 方式2:位置参数(使用 ? 占位符)
query.prepare("INSERT INTO users (name, age) VALUES (?, ?)");
query.addBindValue("Bob");
query.addBindValue(25);
query.exec();

二、高级功能

1. 批量插入数据
// 使用 ODBC 风格的批量插入(高效处理大量数据)
query.prepare("INSERT INTO users (name, age) VALUES (?, ?)");

QVariantList names;
names << "Alice" << "Bob" << "Charlie";

QVariantList ages;
ages << 30 << 25 << 35;

query.addBindValue(names);
query.addBindValue(ages);

if (!query.execBatch()) {
    qDebug() << "Batch error:" << query.lastError().text();
}
2. 执行 DDL 语句(创建表、索引等)
query.exec(R"(
    CREATE TABLE IF NOT EXISTS tasks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        description TEXT,
        priority INTEGER DEFAULT 1,
        due_date DATE,
        completed BOOLEAN DEFAULT 0
    )
)");
3. 获取查询结果元数据
if (query.exec("SELECT * FROM tasks")) {
    QSqlRecord record = query.record();
    int columnCount = record.count();
    
    for (int i = 0; i < columnCount; ++i) {
        qDebug() << "Column" << i << "name:" << record.fieldName(i);
        qDebug() << "Column" << i << "type:" << record.field(i).typeName();
    }
}

三、错误处理

if (!query.exec("SELECT * FROM non_existent_table")) {
    QSqlError error = query.lastError();
    qDebug() << "Error:" << error.text();
    qDebug() << "SQL State:" << error.sqlState();
    qDebug() << "Database Text:" << error.databaseText();
}

四、事务处理

QSqlDatabase db = QSqlDatabase::database();
db.transaction();  // 开始事务

QSqlQuery query;
query.exec("INSERT INTO logs (message) VALUES ('Transaction started')");

if (/* 条件判断 */) {
    db.commit();  // 提交事务
} else {
    db.rollback();  // 回滚事务
}

五、在 TODO 系统中的应用示例

1. 添加任务
bool TodoManager::addTask(const QString &title, const QString &description, int priority, const QDate &dueDate) {
    QSqlQuery query;
    query.prepare("INSERT INTO tasks (title, description, priority, due_date) "
                  "VALUES (:title, :desc, :priority, :dueDate)");
    query.bindValue(":title", title);
    query.bindValue(":desc", description);
    query.bindValue(":priority", priority);
    query.bindValue(":dueDate", dueDate);
    
    return query.exec();
}
2. 查询所有任务
QList<Task> TodoManager::getAllTasks() {
    QList<Task> tasks;
    QSqlQuery query("SELECT * FROM tasks ORDER BY due_date");
    
    while (query.next()) {
        Task task;
        task.id = query.value("id").toInt();
        task.title = query.value("title").toString();
        task.description = query.value("description").toString();
        task.priority = query.value("priority").toInt();
        task.dueDate = query.value("due_date").toDate();
        task.completed = query.value("completed").toBool();
        tasks.append(task);
    }
    
    return tasks;
}
3. 更新任务状态
bool TodoManager::markTaskCompleted(int taskId, bool completed) {
    QSqlQuery query;
    query.prepare("UPDATE tasks SET completed = :completed WHERE id = :id");
    query.bindValue(":completed", completed);
    query.bindValue(":id", taskId);
    return query.exec();
}

六、性能优化建议

  1. 使用预编译语句(Prepared Statements)
    避免重复解析 SQL 语句,提高批量操作效率:

    query.prepare("INSERT INTO items (name, price) VALUES (?, ?)");
    for (const auto &item : itemsList) {
        query.addBindValue(item.name);
        query.addBindValue(item.price);
        query.exec();
    }
    
  2. 批量提交数据
    在事务中执行多个操作,减少数据库提交次数:

    db.transaction();
    // 执行多个INSERT/UPDATE
    db.commit();
    
  3. 使用索引
    对查询频繁的字段(如日期、优先级)创建索引:

    CREATE INDEX idx_due_date ON tasks (due_date);
    

七、注意事项

  1. 数据类型转换
    QSqlQuery::value() 返回 QVariant,需正确转换类型:

    QDate date = query.value("due_date").toDate();  // 正确
    QDate date = query.value("due_date").toString();  // 错误!
    
  2. 跨平台兼容性
    不同数据库(如 SQLite、MySQL、PostgreSQL)对 SQL 语法支持略有差异,需注意兼容性。

  3. 资源管理
    QSqlQuery 对象析构时会自动释放相关资源,无需手动关闭。

总结

QSqlQuery 是 Qt 数据库编程的核心工具,通过以下步骤可高效使用:

  1. 执行 SQL 语句(exec()/prepare()
  2. 处理查询结果(next()/value()
  3. 绑定参数(bindValue()/addBindValue()
  4. 错误处理(lastError()
  5. 事务管理(transaction()/commit()/rollback()

结合参数化查询和事务处理,可确保数据操作的安全性和高效性。在你的 TODO 系统中,QSqlQuery 将是连接 UI 与数据库的关键桥梁。

QSqlQuery::bindValue() 是 Qt 中用于参数化查询的核心函数,它通过将 SQL 语句中的占位符与实际值绑定,实现安全高效的数据查询与操作。以下是对该函数的详细解析:

QSqlQuery::bindvalue函数

一、函数原型与参数

void QSqlQuery::bindValue(const QString &placeholder, const QVariant &val, QSql::ParamType paramType = QSql::In);
  • 参数
    • placeholder:SQL 语句中的占位符名称(如 :name?)。
    • val:要绑定的值,使用 QVariant 支持任意数据类型。
    • paramType:参数类型(可选),默认为 QSql::In(输入参数)。
      • QSql::In:输入参数(用于查询条件)。
      • QSql::Out:输出参数(用于存储存储过程的返回值)。
      • QSql::InOut:输入输出参数。
      • QSql::Binary:二进制数据(如图片、文件)。

二、核心作用与优势

1. 防止 SQL 注入攻击

通过参数化查询,数据库会自动处理值中的特殊字符,避免恶意注入:

// 不安全的拼接(存在 SQL 注入风险)
QString name = userInput;  // 假设用户输入 "Robert'; DROP TABLE users; --"
query.exec("SELECT * FROM users WHERE name = '" + name + "'");

// 安全的参数化查询
query.prepare("SELECT * FROM users WHERE name = :name");
query.bindValue(":name", userInput);  // 自动转义特殊字符
query.exec();
2. 自动类型转换

QVariant 会自动处理数据类型,无需手动格式化:

// 绑定日期类型
QDate dueDate = QDate::currentDate();
query.prepare("INSERT INTO tasks (title, due_date) VALUES (:title, :date)");
query.bindValue(":title", "Buy groceries");
query.bindValue(":date", dueDate);  // 自动转换为数据库支持的日期格式
3. 提高查询性能

预编译 SQL 语句后重复使用,避免数据库重复解析相同结构的 SQL:

query.prepare("INSERT INTO logs (message, timestamp) VALUES (:msg, :time)");

for (const auto &msg : messages) {
    query.bindValue(":msg", msg);
    query.bindValue(":time", QDateTime::currentDateTime());
    query.exec();  // 仅参数变化,SQL 结构不变
}

三、使用示例

1. 命名占位符(推荐方式)
// SQL 语句中使用 :name 形式的占位符
query.prepare("INSERT INTO users (name, age, email) VALUES (:name, :age, :email)");
query.bindValue(":name", "Alice");
query.bindValue(":age", 30);
query.bindValue(":email", "alice@example.com");
query.exec();
2. 位置占位符(使用 ?)
// SQL 语句中使用 ? 占位符
query.prepare("UPDATE tasks SET title = ?, completed = ? WHERE id = ?");
query.bindValue(0, "Revise report");     // 第1个 ?
query.bindValue(1, true);                // 第2个 ?
query.bindValue(2, 5);                   // 第3个 ?
query.exec();
3. 处理 NULL 值
// 绑定可能为 NULL 的值
QString optionalDescription;  // 可能为空
query.prepare("INSERT INTO notes (content, attachment) VALUES (:content, :attach)");
query.bindValue(":content", "Meeting minutes");
query.bindValue(":attach", optionalDescription.isNull() ? QVariant(QVariant::String) : optionalDescription);
4. 存储过程中的输出参数
// 调用存储过程获取用户数量
query.prepare("CALL GetUserCount(:count)");
query.bindValue(":count", 0, QSql::Out);  // 输出参数
query.exec();

int userCount = query.boundValue(":count").toInt();

四、在 TODO 系统中的应用

1. 添加新任务
bool TodoModel::addTask(const Task &task) {
    QSqlQuery query;
    query.prepare("INSERT INTO tasks (title, description, priority, due_date, completed) "
                  "VALUES (:title, :desc, :priority, :dueDate, :completed)");
    query.bindValue(":title", task.title);
    query.bindValue(":desc", task.description);
    query.bindValue(":priority", static_cast<int>(task.priority));  // 枚举转 int
    query.bindValue(":dueDate", task.dueDate);
    query.bindValue(":completed", task.completed);
    
    return query.exec();
}
2. 查询带条件的任务
QList<Task> TodoModel::searchTasks(const QString &keyword, bool showCompleted) {
    QList<Task> results;
    QSqlQuery query;
    
    query.prepare("SELECT * FROM tasks "
                  "WHERE title LIKE :keyword "
                  "AND (completed = :completed OR :showAll = 1)");
    query.bindValue(":keyword", "%" + keyword + "%");
    query.bindValue(":completed", showCompleted);
    query.bindValue(":showAll", showCompleted ? 1 : 0);
    
    if (query.exec()) {
        // 解析结果...
    }
    
    return results;
}

五、注意事项

  1. 占位符语法差异

    • 命名占位符(:name):适用于大多数数据库(如 SQLite、PostgreSQL)。
    • 位置占位符(?):适用于所有数据库,但需严格按顺序绑定值。
  2. 预编译与执行分离

    // 正确流程:先 prepare,再 bindValue,最后 exec
    query.prepare("SELECT * FROM users WHERE age > :min");
    query.bindValue(":min", 18);
    query.exec();
    
  3. 二进制数据处理

    QByteArray imageData;  // 图片数据
    query.bindValue(":image", imageData, QSql::Binary);  // 指定二进制类型
    
  4. 多次执行同一查询

    query.prepare("INSERT INTO items (name) VALUES (:name)");
    
    for (const auto &name : itemNames) {
        query.bindValue(":name", name);
        query.exec();  // 每次执行前重新绑定值
        query.finish();  // 可选:清除当前结果集
    }
    

六、常见问题与解决方案

问题 原因分析 解决方案
查询失败但无明确错误 占位符名称与 bindValue() 不匹配
2. 数据类型不兼容
1. 检查占位符拼写和数量
2. 使用 QVariant::type() 确认类型兼容性
中文或特殊字符乱码 未正确设置数据库连接编码 在连接数据库前设置编码:db.setConnectOptions("MYSQL_OPT_CHARSET_NAME=utf8")
存储过程输出参数无值 1. 未正确标记为 QSql::Out
2. 未在 exec() 后获取值
1. 确保参数类型正确
2. 通过 boundValue() 获取输出值

总结

bindValue() 是 Qt 数据库编程中确保安全性性能的关键函数,主要应用场景包括:

  1. 防止 SQL 注入(优先使用命名占位符)。
  2. 处理复杂数据类型(如日期、二进制)。
  3. 优化重复查询(预编译 SQL)。
  4. 存储过程参数传递。

在 TODO 系统中,建议对所有涉及用户输入的 SQL 语句(如搜索、添加任务)都使用 bindValue(),以避免安全风险并提升代码健壮性。


网站公告

今日签到

点亮在社区的每一天
去签到