You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为QSqlTableModel添加虚拟列失败,请求技术支援

问题:QSqlTableModel新增自定义Role无法正常读写数据

我基于QSqlTableModel实现了一个模型,关联本地数据库的games表,每个Role对应数据库的一列,在QML中展示正常。现在想添加一个额外的Role(虚拟列subgame),但调用setData设置值后,data()始终返回错误值,QML中model.subgame显示为undefined。相关代码如下:

构造函数代码

// 构造函数内添加额外role,并在末尾插入新列
auto rec = record();
for(int i = 0; i < rec.count(); i++) {
    m_roles.insert(Qt::UserRole + i + 1, rec.fieldName(i).toUtf8()); // role名称为列名
}
m_roles.insert(Qt::UserRole + rec.count() + 1, "subgame"); // 额外role "subgame"

select();   

insertColumn(columnCount()); // 新增列
buildCodeMap(); // 填充新列

重写的columnCount和index函数

int SqlTableModel::columnCount(const QModelIndex &parent) const
{
    return QSqlTableModel::columnCount(parent) + 1;
}

QModelIndex SqlTableModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent) && (column == columnCount() - 1)) {
        return createIndex(row, column);
    }

    return QSqlTableModel::index(row, column, parent);
}

data()和setData()函数

QVariant SqlTableModel::data(const QModelIndex &index, int role) const
{
    if (index.isValid()) {
        if (role >= Qt::UserRole) {
            int columnIdx = role - Qt::UserRole - 1;
            QModelIndex modelIndex = this->index(index.row(), columnIdx);
            return QSqlTableModel::data(modelIndex, Qt::DisplayRole);
        }
    }

    return QSqlTableModel::data(index, role);
}

bool SqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (index.isValid()) {
        if (role >= Qt::UserRole) {
            int columnIdx = role - Qt::UserRole - 1;
            QModelIndex modelIndex = this->index(index.row(), columnIdx);
            return QSqlTableModel::setData(modelIndex, value, Qt::EditRole);
        }
    }

    return QSqlTableModel::setData(index, value, role);
}

填充额外列的函数

void SqlTableModel::buildCodeMap()
{
QHash<QString, QStringList> hash;

qDebug() << rowCount();
for (int row = 0; row < rowCount(); ++row) {
    auto tag   = data(index(row, 0), Qt::UserRole + 1).toString(); // tag
    auto code = data(index(row, 0), Qt::UserRole + 6).toString(); // code

    QRegularExpression regex("(.+?)(?:/|$)");
    QRegularExpressionMatch match = regex.match(code);

    if (match.hasMatch()) {
        QString numero = match.captured(1);
        hash[numero] << tag;
        if(hash[numero].count() > 1) {
            setData(index(row, 0), true, Qt::UserRole + 9);
        } else {
            setData(index(row, 0), false, Qt::UserRole + 9);
        }
    }

    qDebug() << data(index(row, 0), Qt::UserRole + 9).toBool(); // subgame
}
}

roleNames()函数

QHash<int, QByteArray> SqlTableModel::roleNames() const 
{ 
   return m_roles;
}

问题原因分析

  1. 错误操作数据库列:调用insertColumn(columnCount())会尝试在数据库的games表中新增一列,但实际需要的是模型层面的虚拟列,不需要修改数据库。QSqlTableModel与数据库强绑定,新增数据库不存在的列会导致数据读写异常。
  2. 未存储虚拟列数据:QSqlTableModel不会自动维护额外列的数据,当前代码尝试通过父类setData写入,父类会操作数据库,必然失败。
  3. Role与列的映射逻辑错误:data()函数将自定义Role直接转换为列索引后调用父类data,但父类没有虚拟列的数据,返回空值;同时buildCodeMap中硬编码Qt::UserRole +9,若数据库列数变化,会导致Role不匹配。
  4. 未正确触发数据更新信号:即使设置了内部数据,若未发出dataChanged信号,QML无法感知数据变化,导致显示undefined。

解决办法

步骤1:添加内部数据存储

在SqlTableModel类中添加成员变量,用于存储虚拟列subgame的数据:

private:
    QVector<QVariant> m_subgameData;

步骤2:修正构造函数逻辑

移除insertColumn调用,初始化内部数据容器:

auto rec = record();
for(int i = 0; i < rec.count(); i++) {
    m_roles.insert(Qt::UserRole + i + 1, rec.fieldName(i).toUtf8());
}
// 先获取subgame对应的Role值,避免硬编码
int subgameRole = Qt::UserRole + rec.count() + 1;
m_roles.insert(subgameRole, "subgame");

select();

// 初始化虚拟列数据容器,大小与行数一致
m_subgameData.resize(rowCount());
m_subgameData.fill(false); // 默认值

buildCodeMap();

步骤3:重写data()函数

区分处理虚拟列和数据库列的请求:

QVariant SqlTableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid()) {
        return QVariant();
    }

    // 处理虚拟列的列索引请求
    int virtualColumn = columnCount() - 1;
    if (index.column() == virtualColumn) {
        if (index.row() >= 0 && index.row() < m_subgameData.size()) {
            return m_subgameData.at(index.row());
        }
        return QVariant();
    }

    // 处理自定义Role请求
    if (role >= Qt::UserRole) {
        // 查找Role对应的列名或虚拟Role
        auto it = m_roles.find(role);
        if (it != m_roles.end()) {
            if (*it == "subgame") {
                if (index.row() >=0 && index.row() < m_subgameData.size()) {
                    return m_subgameData.at(index.row());
                }
            } else {
                // 数据库列的Role请求,转成列索引调用父类
                int columnIdx = rec().indexOf(*it);
                if (columnIdx != -1) {
                    QModelIndex dbIndex = QSqlTableModel::index(index.row(), columnIdx);
                    return QSqlTableModel::data(dbIndex, Qt::DisplayRole);
                }
            }
        }
    }

    // 其他情况交给父类处理
    return QSqlTableModel::data(index, role);
}

步骤4:重写setData()函数

处理虚拟列的数据更新,并发出dataChanged信号:

bool SqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid()) {
        return false;
    }

    int virtualColumn = columnCount() -1;
    // 处理虚拟列的列索引更新
    if (index.column() == virtualColumn) {
        if (index.row() >=0 && index.row() < m_subgameData.size()) {
            m_subgameData[index.row()] = value;
            emit dataChanged(index, index, {role});
            return true;
        }
        return false;
    }

    // 处理自定义Role更新
    if (role >= Qt::UserRole) {
        auto it = m_roles.find(role);
        if (it != m_roles.end()) {
            if (*it == "subgame") {
                if (index.row() >=0 && index.row() < m_subgameData.size()) {
                    m_subgameData[index.row()] = value;
                    QModelIndex virtualIndex = createIndex(index.row(), virtualColumn);
                    emit dataChanged(virtualIndex, virtualIndex, {role});
                    return true;
                }
            } else {
                // 数据库列的Role更新,转成列索引调用父类
                int columnIdx = rec().indexOf(*it);
                if (columnIdx != -1) {
                    QModelIndex dbIndex = QSqlTableModel::index(index.row(), columnIdx);
                    return QSqlTableModel::setData(dbIndex, value, Qt::EditRole);
                }
            }
        }
    }

    // 其他情况交给父类处理
    return QSqlTableModel::setData(index, value, role);
}

步骤5:修正buildCodeMap()函数

使用正确的Role值,避免硬编码:

void SqlTableModel::buildCodeMap()
{
    QHash<QString, QStringList> hash;
    int rowCount = this->rowCount();
    qDebug() << rowCount;

    // 获取subgame对应的Role
    int subgameRole = -1;
    for (auto it = m_roles.constBegin(); it != m_roles.constEnd(); ++it) {
        if (*it == "subgame") {
            subgameRole = it.key();
            break;
        }
    }
    if (subgameRole == -1) {
        return;
    }

    for (int row = 0; row < rowCount; ++row) {
        // 通过列名获取数据,避免硬编码Role索引
        auto tag = data(index(row, rec().indexOf("tag")), Qt::DisplayRole).toString();
        auto code = data(index(row, rec().indexOf("code")), Qt::DisplayRole).toString();

        QRegularExpression regex("(.+?)(?:/|$)");
        QRegularExpressionMatch match = regex.match(code);

        if (match.hasMatch()) {
            QString numero = match.captured(1);
            hash[numero] << tag;
            bool isSubgame = hash[numero].count() > 1;
            setData(index(row, 0), isSubgame, subgameRole);
            qDebug() << data(index(row, 0), subgameRole).toBool();
        }
    }
}

步骤6:修正index()函数

确保虚拟列的索引创建正确:

QModelIndex SqlTableModel::index(int row, int column, const QModelIndex &parent) const
{
    int virtualColumn = columnCount() -1;
    if (column == virtualColumn && row >=0 && row < rowCount() && !parent.isValid()) {
        return createIndex(row, column);
    }
    return QSqlTableModel::index(row, column, parent);
}

内容的提问来源于stack exchange,提问作者palador

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 11:32:04