【问题标题】:Why can I emit dataChanged(), but not layoutChanged() in a PySide2 table model?为什么我可以在 PySide2 表模型中发出 dataChanged(),但不能发出 layoutChanged()?
【发布时间】:2020-05-23 11:05:28
【问题描述】:

我是 Qt 的新手。目前我正在尝试学习如何从不同的线程更新表模型,然后如何为其获取即时显示更新。我阅读了文档并找到了dataChanged()layoutChanged() 信号。虽然dataChanged() 工作正常,但任何发出layoutChanged() 的尝试都会失败:

'QObject::connect: Cannot queue arguments of type 'QList<QPersistentModelIndex>' (Make sure 'QList<QPersistentModelIndex>' is registered using qRegisterMetaType().)

搜索这个特定的错误并没有给我任何可以转化为工作代码的东西。我没有明确使用任何QListQPersistentModelIndex,但由于我选择的构造,当然可以隐式使用。

我做错了什么?

class TimedModel(QtCore.QAbstractTableModel):

    def __init__(self, table, view):
        super(TimedModel, self).__init__()
        self.table = table
        self.view =  view
        self.setHeaderData(0, Qt.Horizontal, Qt.AlignLeft, Qt.TextAlignmentRole)
        self.rows = 6
        self.columns = 4
        self.step = 5
        self.timer = Thread(
            name = "Timer",
            target = self.tableTimer,
            daemon = True)
        self.timer.start()
        self.random = Random()
        self.updated = set()

    @staticmethod
    def encode(row, column):
        return row << 32 | column

    def data(self, index, role):

        if role == Qt.DisplayRole or role == Qt.EditRole:
            return f'Data-{index.row()}-{index.column()}'

        if role == Qt.ForegroundRole:
            encoded = TimedModel.encode(index.row(), index.column())
            return QBrush(Qt.red if encoded in self.updated else Qt.black)            

        return None

    def rowCount(self, index):
        return self.rows

    def columnCount(self, index):
        return self.columns

    def headerData(self, col, orientation, role):
        if orientation == Qt.Vertical:
            # Vertical
            return super().headerData(col, orientation, role)
        # Horizontal
        if not 0 <= col < self.columns:
            return None
        if role == Qt.DisplayRole:
            return f'Data-{col}'
        if role == Qt.TextAlignmentRole:
            return int(Qt.AlignLeft | Qt.AlignVCenter)
        return super().headerData(col, orientation, role)

    def tableTimer(self):
        while True:
            time.sleep(5.0)
            randomRow = self.random.randint(0, self.rows)
            randomColumn = self.random.randint(0, self.columns)
            encodedRandom = TimedModel.encode(randomRow, randomColumn)
            if encodedRandom in self.updated:
                self.updated.remove(encodedRandom)
            else:
                self.updated.add(encodedRandom)
            updatedIndex = self.createIndex(randomRow, randomColumn)
            self.dataChanged.emit(updatedIndex, updatedIndex)

            '''this here does not work:'''
            self.layoutAboutToBeChanged.emit()
            self.rows += self.step
            self.layoutChanged.emit()

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)

        self.timedTable = QTableView()
        self.model = TimedModel(self.timedTable, self)

        self.timedTable.setModel(self.model)
        headerView = self.timedTable.horizontalHeader()
        headerView.setStretchLastSection(True)
        self.setCentralWidget(self.timedTable)

        self.setGeometry(300, 300, 1000, 600)
        self.setWindowTitle('Timed Table')
        self.show()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    app.name = "Timed Table Application"
    window = MainWindow()
    window.show()
    app.exec_()

【问题讨论】:

    标签: python pyside2


    【解决方案1】:

    以下代码:

    self.layoutAboutToBeChanged.emit()
    self.rows += self.step
    self.layoutChanged.emit()
    

    创建具有QPersistentModelIndex 关联但不是线程安全的新模型元素,并且 Qt 监视其创建以警告其滥用,因为在这种情况下修改该元素是不安全的,因为它意味着从另一个线程修改 GUI(阅读here 了解更多信息)。

    因此,您会看到该消息警告您尝试执行的操作不安全。

    相反,dataChanged 只发出一个信号,不创建任何属于 Qt 的元素,而且您很幸运,“self.updated”的修改没有产生瓶颈,因为您修改了属于主线程的属性从不使用守卫作为互斥体的辅助线程。

    Qt 指出 GUI 和 GUI 使用的元素应该只在 GUI 线程中更新,如果你想用来自另一个线程的信息修改 GUI,那么你必须发送该信息,例如,使用线程安全的信号:

    import random
    import sys
    import threading
    import time
    
    from PySide2 import QtCore, QtGui, QtWidgets
    
    
    class TimedModel(QtCore.QAbstractTableModel):
        random_signal = QtCore.Signal(object)
    
        def __init__(self, table, view):
            super(TimedModel, self).__init__()
            self.table = table
            self.view = view
            self.setHeaderData(
                0, QtCore.Qt.Horizontal, QtCore.Qt.AlignLeft, QtCore.Qt.TextAlignmentRole
            )
            self.rows = 6
            self.columns = 4
            self.step = 5
            self.updated = set()
    
            self.random_signal.connect(self.random_slot)
    
            self.timer = threading.Thread(name="Timer", target=self.tableTimer, daemon=True)
            self.timer.start()
    
        @staticmethod
        def encode(row, column):
            return row << 32 | column
    
        def data(self, index, role):
    
            if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
                return f"Data-{index.row()}-{index.column()}"
    
            if role == QtCore.Qt.ForegroundRole:
                encoded = TimedModel.encode(index.row(), index.column())
                return QtGui.QBrush(
                    QtCore.Qt.red if encoded in self.updated else QtCore.Qt.black
                )
    
            return None
    
        def rowCount(self, index):
            return self.rows
    
        def columnCount(self, index):
            return self.columns
    
        def headerData(self, col, orientation, role):
            if orientation == QtCore.Qt.Vertical:
                # Vertical
                return super().headerData(col, orientation, role)
            # Horizontal
            if not 0 <= col < self.columns:
                return None
            if role == QtCore.Qt.DisplayRole:
                return f"Data-{col}"
            if role == QtCore.Qt.TextAlignmentRole:
                return QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
            return super().headerData(col, orientation, role)
    
        def tableTimer(self):
            while True:
                time.sleep(5.0)
                randomRow = random.randint(0, self.rows)
                randomColumn = random.randint(0, self.columns)
                encodedRandom = TimedModel.encode(randomRow, randomColumn)
    
                self.random_signal.emit(encodedRandom)
    
        @QtCore.Slot(object)
        def random_slot(self, encodedRandom):
            if encodedRandom in self.updated:
                self.updated.remove(encodedRandom)
            else:
                self.updated.add(encodedRandom)
            self.layoutAboutToBeChanged.emit()
            self.rows += self.step
            self.layoutChanged.emit()
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.timedTable = QtWidgets.QTableView()
            self.model = TimedModel(self.timedTable, self)
    
            self.timedTable.setModel(self.model)
            headerView = self.timedTable.horizontalHeader()
            headerView.setStretchLastSection(True)
            self.setCentralWidget(self.timedTable)
    
            self.setGeometry(300, 300, 1000, 600)
            self.setWindowTitle("Timed Table")
            self.show()
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        app.name = "Timed Table Application"
        window = MainWindow()
        window.show()
        app.exec_()
    

    【讨论】:

    • 完成。与此同时,我将您的解决方案移植到了我的实际项目中。像一首歌一样工作。不幸的是,我没有足够的声誉将您的答案标记为有用...
    猜你喜欢
    • 2021-10-03
    • 2016-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    相关资源
    最近更新 更多