PySide Qt中dataChanged.emit无效但layoutChanged.emit可触发带QML报错问题
PySide6 QAbstractListModel更新不触发界面及QML报错问题解决方案
问题原因
- 新增行调用
dataChanged无效的原因:dataChanged信号仅用于通知视图已存在的行的数据发生修改,不适用于新增/删除行的场景。且原代码中dataChanged.emit传入的参数完全错误:self.index是模型的索引生成方法,并未调用生成实际新增行的索引,信号参数不合法自然无法触发更新。 - 调用
layoutChanged触发QML报错的原因:layoutChanged是全量布局重置信号,会触发视图销毁所有已渲染的列表项再全部重建,若QML委托存在未完成的属性绑定、动画逻辑,旧列表项销毁后绑定的对象变为null,就会抛出访问null属性的报错,同时该方法性能极差,完全不适合单条数据新增的场景。
解决方案
Qt的QAbstractItemModel家族有严格的更新约定:所有修改模型结构(新增/删除/移动行)的操作,必须在修改内部数据前调用对应的beginXXX方法,修改完成后调用对应的endXXX方法,由这两个方法自动发送合规的信号通知视图增量更新。
修改后完整代码如下:
import sys import random from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType from PySide6.QtCore import QObject, Signal, Slot, Property, Qt, QUrl, QTimer, QAbstractListModel, QModelIndex class CpuLoadModel(QAbstractListModel): def __init__(self): super().__init__() self.__update_timer = QTimer(self) self.__update_timer.setInterval(1000) self.__update_timer.timeout.connect(self.__update) self.__update_timer.start() self.todos = [] def __update(self): new_row_pos = len(self.todos) # 新增行前调用beginInsertRows,传入父索引(列表为无效索引)、新增行的起止位置 self.beginInsertRows(QModelIndex(), new_row_pos, new_row_pos) self.todos.append(random.randint(0, 99)) # 数据修改完成后调用endInsertRows,自动通知视图增量更新 self.endInsertRows() print(self.todos) def data(self, index, role=Qt.DisplayRole): if not index.isValid() or index.row() >= len(self.todos): return None if role == Qt.DisplayRole: return self.todos[index.row()] return None def rowCount(self, index=QModelIndex()): # 符合Qt模型约定:有效父索引下返回0(列表无层级结构) if index.isValid(): return 0 return len(self.todos) if __name__ == '__main__': app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() qmlRegisterType(CpuLoadModel, 'PsUtils', 1, 0, 'CpuLoadModel') engine.load(QUrl("main.qml")) if not engine.rootObjects(): sys.exit(-1) sys.exit(app.exec())
核心修改点
- 新增行时使用
beginInsertRows+endInsertRows的标准流程,无需额外发送dataChanged或layoutChanged信号 - 给
data和rowCount方法增加了索引合法性校验,符合Qt模型接口规范,避免边界场景报错 - 补充了缺失的依赖导入,优化了父类初始化写法
修改后视图只会增量渲染新增的一行,不会销毁旧列表项,既不会出现界面不更新的问题,也不会触发QML端的空对象访问报错。
内容的提问来源于stack exchange,提问作者David Leopoldie
相关产品推荐
相关产品推荐

