Почему я могу выдать dataChanged(), но не layoutChanged() в табличной модели PySide2?

Я новичок в Qt. В настоящее время я пытаюсь узнать, как обновить модель таблицы из другого потока, а затем как получить для нее немедленное обновление дисплея. Я прочитал документацию и нашел сигналы dataChanged() и layoutChanged(). В то время как dataChanged() работает нормально, любая попытка испустить layoutChanged() терпит неудачу с:

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

Поиск этой конкретной ошибки не дал мне ничего, что я мог бы превратить в рабочий код. Я не использую никакие QList или QPersistentModelIndex явно, но, конечно, их можно использовать неявно благодаря конструкциям, которые я выбрал.

Что я делаю не так?

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

person Chris Frisk    schedule 23.05.2020    source источник


Ответы (1)


Следующий код:

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

создавать новые элементы модели, связанные с QPersistentModelIndex, которые не являются потокобезопасными, и что Qt отслеживает их создание, чтобы предупредить их неправильное использование, как в этом случае, поскольку изменение этого элемента небезопасно, поскольку подразумевает изменение графического интерфейса из другого потока (Читайте здесь для получения дополнительной информации).

Итак, вы видите это сообщение, предупреждающее о том, что то, что вы пытаетесь сделать, небезопасно.

Вместо этого dataChanged только выдает сигнал, не создает никаких элементов, принадлежащих Qt, и вам повезло, что модификация "self.updated" не создала узких мест, поскольку вы изменяете свойство, принадлежащее основному потоку, из вторичного потока без использовать охранников в качестве мьютексов.

Qt указывает, что графический интерфейс и элементы, используемые графическим интерфейсом, должны обновляться только в потоке графического интерфейса, и если вы хотите изменить графический интерфейс с помощью информации из другого потока, вы должны отправить эту информацию, например, с помощью сигналов, которые потокобезопасны:

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_()
person eyllanesc    schedule 23.05.2020
comment
Сделанный. Тем временем я перенес ваше решение в свой реальный проект. Работает как песня. К сожалению, у меня недостаточно репутации, чтобы отметить ваш ответ как полезный... - person Chris Frisk; 24.05.2020