QTableView-如何处理很多dataChanged信号?

时间:2018-10-18 12:05:58

标签: python qt qabstractitemmodel pyside2 qabstractitemview

我必须编写一个表格视图,该表格视图应该能够处理成千上万个包含图像的单元格(每个单元格的图像都不同)。一切都在python中使用PySide2。

我使用线程池实现了图像的加载。 问题是我必须为给定索引异步通知视图图像已加载,以便它可以重新加载显示。使用 dataChanged信号可以工作,但是其中太多需要处理,并且直到线程池处理完所有索引后,UI才会显示。

我在下面提供了一个工作示例,该示例重现了该问题(没有图像,正则文本)。 现在,我通过让线程稍微休眠(只是取消注释Work.run方法中的time.sleep(1)行)解决了该问题,但它感觉更像是一个肮脏的hack,而不是真正的解决方案。

我考虑了以下解决方案:

  • 尝试使dataChanged信号异步运行。我怀疑dataChanged和将视图更新为自动连接的任何插槽之间的默认连接。有什么办法吗?
  • 将修改后的索引收集到缓冲区中,并以固定的时间间隔更新视图。我想避免这种解决方案,因为要在两次缓冲区评估之间找到一个良好的时间间隔是一项艰巨的任务。

关于如何避免这种阻止行为,您还有其他想法吗?

谢谢您的建议!

import math
from random import choice
import string
import time

from PySide2.QtCore import QModelIndex
from PySide2.QtCore import Qt
from PySide2.QtCore import QObject
from PySide2.QtCore import Signal
from PySide2.QtCore import QThread
from PySide2.QtCore import QThreadPool
from PySide2.QtCore import QMutex
from PySide2.QtCore import QAbstractTableModel
from PySide2.QtWidgets import QTableView


class Notifier(QObject):

    finished = Signal(QModelIndex, str)


class Work(QThread):

    def __init__(self, *args, **kwargs):
        super(Work, self).__init__(*args, **kwargs)
        self.work = []
        self._stopped = False
        self._slept = False

    def run(self):

        while True:

            try:
                work = self.work.pop(0)
            except IndexError:
                work = None

            if not work:
                if self._slept:
                    break

                self.msleep(500)
                self._slept = True
                continue

            # Uncomment the following line to make the UI responsive
            # time.sleep(1)

            if work[0]:
                c = ''.join(choice(string.ascii_uppercase + string.digits)
                            for _ in range(6))
                work[0].finished.emit(work[1], c)

    def reset(self):
        self.work = []
        self._stopped = True


class WorkPool(object):

    def __init__(self):

        self.thread_count = QThreadPool().maxThreadCount()
        self.thread_pool = []
        self.thread_cpt = 0
        self.mutex = QMutex()

        for c in range(0, self.thread_count):
            self.thread_pool.append(Work())

    def add_work(self, notifier, index):

        new_thread = divmod(self.thread_cpt, self.thread_count)[1]
        thread = self.thread_pool[new_thread]
        self.thread_cpt += 1

        thread.work.append((notifier, index))

        if not thread.isRunning():
            thread.start()

    def terminate(self):
        self.mutex.lock()

        for t in self.thread_pool:
            t.reset()

        for t in self.thread_pool:
            t.wait()

        self.mutex.unlock()


class TableModel(QAbstractTableModel):

    def __init__(self, items, *args, **kwargs):
        super(TableModel, self).__init__(*args, **kwargs)

        self.items = items
        self.works = []
        self.loader = WorkPool()

    def index(self, row, column, parent=QModelIndex()):

        pos = row * self.columnCount() + column

        try:
            return self.createIndex(row, column,self.items[pos])

        except IndexError:
            return QModelIndex()


    def data(self, index, role):

        if not index.isValid():
            return None

        if role == Qt.DisplayRole:
            return index.internalPointer()

    def columnCount(self, parent=QModelIndex()):
        return 10

    def rowCount(self, parent=QModelIndex()):
        return int(math.ceil(float(len(self.items)) / self.columnCount()))

    def refresh_content(self):

        # Launch a thread to update the content of each index
        for r in range(0, self.rowCount()):
            for c in range(0, self.columnCount()):
                index = self.index(r, c)

                notifier = Notifier()
                notifier.finished.connect(self.setData)

                self.loader.add_work(notifier, index)

    def setData(self, index, value):

        if not index.isValid():
            return False

        self.items[index.row() * self.columnCount() + index.column()] = value
        self.dataChanged.emit(index, index)
        return True



class TableView(QTableView):

    def closeEvent(self, *args, **kwargs):
        self.model().loader.terminate()
        super(TableView, self).closeEvent(*args, **kwargs)


if __name__ == '__main__':

    from PySide2.QtWidgets import QApplication

    app = QApplication([])

    tv = TableView()

    model = TableModel([None] * 99999)
    tv.setModel(model)
    model.refresh_content()

    tv.show()

    app.exec_()

0 个答案:

没有答案