QSqlQueryModel 行数正确却显示空白,为什么还会递归报错?
问题出在重写的 data():它没有处理 EditRole,也没有将其他角色交给父类。record(row) 内部会通过 EditRole 读取各列,因此第一种写法得到 None。第二种写法把 record(row) 移到角色判断之前,又导致了递归调用。
可以在自定义角色的处理中直接调用 super().data(),获取目标列的数据,并在方法末尾保留父类回退。
为什么行数和列数正确,字段值却是 None?
Qt 的 QSqlQueryModel::record(row) 会复制字段结构,再逐列调用模型的虚函数 data(..., Qt::EditRole) 填入值,从而进入你重写的 Python data()。具体调用关系可见 Qt 源码。
你的实现只处理了 DisplayRole 和 ToolTipRole。收到 EditRole 时,函数直接结束,Python 隐式返回 None。调用过程如下:
data(index, DisplayRole)
→ record(row)
→ data(各列索引, EditRole)
→ 返回 None
→ record.value("name") 得到 None
记录仍保留 3 个字段,所以 count() 为 3。行数也不取决于显示角色返回的内容,因此可能出现“有 4 行,但全部空白”的现象。
为什么提前读取 record(row) 会递归?
第二种写法在判断角色之前执行:
rec = self.record(r)
当 record(r) 内部请求 EditRole 时,又会进入同一个 data(),再次执行 self.record(r):
data()
→ record(row)
→ data(..., EditRole)
→ record(row)
→ data(..., EditRole)
→ ...
递归发生在角色判断之前,因此补上后面的 if 分支也无法阻止它。
如何实现姓名显示和年龄提示?
对于原查询 SELECT id, name, age FROM people,列号依次为 0、1、2。可以将 ListModel 替换为:
class ListModel(QSqlQueryModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setQuery("SELECT id, name, age FROM people")
if self.lastError().isValid():
raise RuntimeError(self.lastError().text())
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if not index.isValid():
return None
if role == Qt.ItemDataRole.DisplayRole:
name_index = self.index(index.row(), 1)
return super().data(
name_index, Qt.ItemDataRole.DisplayRole
)
if role == Qt.ItemDataRole.ToolTipRole:
age_index = self.index(index.row(), 2)
age = super().data(
age_index, Qt.ItemDataRole.EditRole
)
return None if age is None else str(age)
return super().data(index, role)
代码通过 super().data() 调用父类实现,直接读取对应列,避免再次进入当前重写的方法。年龄转换为字符串后返回,供工具提示显示。
这段代码已经把 DisplayRole 映射到姓名列,因此不需要另外设置 setModelColumn(1)。末尾的父类回退保留了 EditRole 等角色的默认行为。
可以继续使用 record(row) 吗?
可以。对于第一种写法,只要在末尾添加:
return super().data(index, role)
并继续将 self.record(r) 放在 DisplayRole、ToolTipRole 分支内部,就能让内部的 EditRole 请求正常交给父类。
不要把 record(r) 无条件放在方法开头。即使写成 super().record(r),该方法内部仍会通过虚函数调用你的 data(),因此无法绕开递归。
如果只是想按字段名称查找列号,可以使用无参数的 self.record().indexOf("name")。无参数的 record() 提供字段信息,不负责填入某一行的数据,参见 Qt 的 record() 文档。
还有哪些注意事项?
- 改用
QSqlTableModel不能自动修复重写方法中的角色处理问题。仍应保留super().data(index, role),让对应父类处理默认取值。 - 示例中的固定列号依赖当前
SELECT顺序;调整查询字段后,要同步修改映射。 - 主程序应先创建
QApplication,再初始化 SQL 连接和模型。初始化时也应检查con.open()与query.exec()的返回值。查询模型的错误可通过 lastError() 查看。
备注:内容仅供参考。
