简体   繁体   English

为什么我可以在 PySide2 表 model 中发出 dataChanged(),但不能发出 layoutChanged()?

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

I am new to Qt.我是 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.目前,我正在尝试学习如何从不同的线程更新表 model,然后如何为其获取即时显示更新。 I read the documentation and found the dataChanged() and layoutChanged() signals.我阅读了文档并找到了dataChanged()layoutChanged()信号。 While dataChanged() works fine, any attempt to emit layoutChanged() fails with:虽然dataChanged()工作正常,但任何发出layoutChanged()的尝试都会失败:

'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.我没有显式使用任何QListQPersistentModelIndex ,但由于我选择的构造,当然可以隐式使用它们。

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).创建新的 model 元素,这些元素具有关联的QPersistentModelIndex不是线程安全的,并且 Qt 监视其创建以警告其滥用,因为在这种情况下修改该元素是不安全的,因为它意味着从另一个线程修改 GUI(阅读此处了解更多信息) .

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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