简体   繁体   中英

Why can I emit dataChanged(), but not layoutChanged() in a PySide2 table model?

I am new to Qt. Currently I am trying to learn how to update a table model from a different thread and then how to get an immediate display update for it. I read the documentation and found the dataChanged() and layoutChanged() signals. While dataChanged() works fine, any attempt to emit layoutChanged() fails with:

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

Searching for this particular error didn't give me anything that I could turn into working code. I am not using any QList or QPersistentModelIndex explicitly, but of course that can be implicitly used due to the constructs that I chose.

What am I doing wrong?

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_()

The following code:

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

create new model elements that have QPersistentModelIndex associated that are not thread-safe and that Qt monitors its creation to warn its misuse as in this case since modifying that element is unsafe since it implies modifying the GUI from another thread (Read here for more information).

So you see that message warning that what you are trying to do is unsafe.

Instead dataChanged only emits a signal, does not create any element belonging to Qt, and you have been lucky that the modification of "self.updated" has not generated bottlenecks since you modify a property that belongs to the main thread from a secondary thread without use guards as mutexes.

Qt points out that the GUI and the elements that the GUI uses should only be updated in the GUI thread, and if you want to modify the GUI with information from another thread, then you must send that information, for example, using the signals that are thread- safe:

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_()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM