为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; }
问题原因分析
- 错误操作数据库列:调用
insertColumn(columnCount())会尝试在数据库的games表中新增一列,但实际需要的是模型层面的虚拟列,不需要修改数据库。QSqlTableModel与数据库强绑定,新增数据库不存在的列会导致数据读写异常。 - 未存储虚拟列数据:
QSqlTableModel不会自动维护额外列的数据,当前代码尝试通过父类setData写入,父类会操作数据库,必然失败。 - Role与列的映射逻辑错误:
data()函数将自定义Role直接转换为列索引后调用父类data,但父类没有虚拟列的数据,返回空值;同时buildCodeMap中硬编码Qt::UserRole +9,若数据库列数变化,会导致Role不匹配。 - 未正确触发数据更新信号:即使设置了内部数据,若未发出
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
相关产品推荐
相关产品推荐

