如何在不冻结主UI的情况下更新QStandartItemModel

1 投票
1 回答
1300 浏览
提问于 2025-04-17 06:13

我正在学习PyQt4,但在某个问题上卡了很久,自己搞不定:

这里的情况是:有一个树形视图(TreeView),使用了自定义的QStandardItemModel,每隔几秒就会重建一次,而且可能会有很多条目(至少几百条)。另外,不同的列还会有额外的代理(delegates)。这个过程比较复杂,即使是简单的模型,没有代理,构建时间也能达到0.3秒,这样会导致树形视图卡顿。

请给我一些建议,看看怎么解决这个问题。我在想是否可以在不同的线程中构建模型,然后把它发送到树形视图,树形视图只需用新的模型调用setModel(),但我没能做到。

这里有一些代码,可能能稍微说明一下问题:

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os, re, time

app = QApplication(sys.argv)
REFRESH = 1

class Reloader_Thread(QThread):
    def __init__(self, parent = None):
        QThread.__init__(self, parent)
        self.loaders = ['\\', '--', '|', '/', '--']
        self.emit(SIGNAL('refresh'))
    def run(self):
        format = '|%d/%b/%Y %H:%M:%S| '
        while True:
            self.emit(SIGNAL('refresh'))
            self.sleep(REFRESH)

class Model(QStandardItemModel):
    def __init__(self, viewer=None):
        QStandardItemModel.__init__(self,None)
        self.build()

    def build(self):
        stTime = time.clock()
        newRows = []
        for r in range(1000):
            row = []
            for c in range(12):
                item = QStandardItem('%s %02d%02d' % (time.strftime('%H"%M\'%S'), r,c))
                row.append(item)
            newRows.append(row)
        eTime = time.clock() - stTime
        outStr = 'Build %03f' % eTime
        format = '|%d/%b/%Y %H:%M:%S| '
        stTime = time.clock()
        self.beginRemoveRows(QModelIndex(),  0, self.rowCount())
        self.removeRows(0, self.rowCount())
        self.endRemoveRows()
        eTime = time.clock() - stTime
        outStr += ', Remove %03f' % eTime
        stTime = time.clock()
        numNew = len(newRows)
        for r in range(numNew):
            self.appendRow(newRows[r])
        eTime = time.clock() - stTime
        outStr += ', Set %03f' % eTime
        self.emit(SIGNAL('status'), outStr)
        self.reset()

w = QWidget()
w.setGeometry(200,200,800,600)
hb = QVBoxLayout(w)
tv = QTreeView()
tvm = Model(tv)
tv.setModel(tvm)

sb = QStatusBar()
reloader = Reloader_Thread()
tvm.connect(tvm, SIGNAL('status'), sb.showMessage)
reloader.connect(reloader, SIGNAL('refresh'), tvm.build)
reloader.start()

hb.addWidget(tv)
hb.addWidget(sb)
w.show()
app.setStyle('plastique')
app.processEvents(QEventLoop.AllEvents)
app.aboutToQuit.connect(reloader.quit)
app.exec_()

谢谢你的建议。到目前为止,我的情况是:每次刷新时,我都会构建一个新的模型并发送给树形视图……这个过程很快,但我不知道当前树形视图的模型发生了什么,也不知道该如何处理。此外,我发现我的应用程序占用的内存在不断增加。

还有一点,我想保留我的选择,但是基于条目数据,而不是视觉上的矩形或行的顺序,所以我也做到了这一点,但看起来有点乱,不像是正确的方法。如果能在这方面得到帮助,我也会很感激。代码如下:

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os, re, time

app = QApplication(sys.argv)
REFRESH = 1

class Reloader_Thread(QThread):
    def __init__(self, parent = None):
        QThread.__init__(self, parent)
        self.moveToThread(self)

    def run(self):
        while True:
            model = Model()
            #model.connect(model, SIGNAL('status'), self.emitStat)
            if model.build():
                self.emit(SIGNAL('refresh'), model)
            self.sleep(REFRESH)

    def emitStat(self, stat):
        self.emit(SIGNAL('status'), stat)

class Tree(QTreeView):
    def __init__(self, parent=None):
        QTreeView.__init__(self, parent)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)

    def resetModel(self, model):
        stTime = time.clock()
        # gather old selection
        oldSel = set()
        currModel = self.model()
        for index in self.selectedIndexes():
            id = currModel.itemFromIndex(index).data().toString()
            oldSel.add('%s'%id)
        # setup new
        self.setModel(model)
        selModel = self.selectionModel()
        for r in range(model.rowCount()):
            item = model.item(r,0)
            rowId = '%s' % item.data().toString()
            if rowId in oldSel:
                sel = QItemSelection(model.index(r,0), model.index(r,model.columnCount()-1))
                selModel.select(sel, QItemSelectionModel.Select)
        self.setSelectionModel(selModel)
        self.emit(SIGNAL('status'), 'TV setModel: %03fs' % (time.clock() - stTime))

class Model(QStandardItemModel):
    def __init__(self, viewer=None):
        QStandardItemModel.__init__(self,None)

    def build(self):
        stTime = time.clock()
        newRows = []
        for r in range(1000):
            row = []
            var = QVariant('%d'%r)
            for c in range(12):
                item = QStandardItem('%s r%02dc%02d' % (time.strftime('%H"%M\'%S'), r,c))
                item.setData(var)
                row.append(item)
            newRows.append(row)
        eTime = time.clock() - stTime
        outStr = 'Build %03f' % eTime
        format = '|%d/%b/%Y %H:%M:%S| '
        stTime = time.clock()
        self.beginRemoveRows(QModelIndex(),  0, self.rowCount())
        self.removeRows(0, self.rowCount())
        self.endRemoveRows()
        eTime = time.clock() - stTime
        outStr += ', Remove %03f' % eTime
        stTime = time.clock()
        numNew = len(newRows)
        for r in range(numNew):
            self.appendRow(newRows[r])
        eTime = time.clock() - stTime
        outStr += ', Set %03f' % eTime
        self.emit(SIGNAL('status'), outStr)
        #self.reset()
        return True

w = QWidget()
w.setGeometry(200,200,800,600)
hb = QVBoxLayout(w)
tv = Tree()

sb = QStatusBar()
reloader = Reloader_Thread()
tv.connect(tv, SIGNAL('status'), sb.showMessage)
reloader.connect(reloader, SIGNAL('refresh'), tv.resetModel)
reloader.connect(reloader, SIGNAL('status'), sb.showMessage)
reloader.start()

hb.addWidget(tv)
hb.addWidget(sb)
w.show()
app.setStyle('plastique')
app.processEvents(QEventLoop.AllEvents)
app.aboutToQuit.connect(reloader.quit)
app.exec_()

1 个回答

1

你想的方向是对的。

你需要一个单独的工作线程来帮你重新计算模型(可以根据时间或者信号来决定多久计算一次)。然后,当计算完成后,你需要设置一个信号来通知主线程。

不过,有几个注意事项你要知道。

  1. QObject 是在线程中运行的。在 C++ 的 QT 中,信号和槽的机制默认只在创建它们的线程内工作。如果你想在不同的线程之间发送信号,你需要特别指定一下(可以查阅信号/连接的文档)。

  2. 为了在工作线程中使用模型,你需要把模型“移动”到工作线程中(应该有一个叫做 movetothread 的方法或者类似的东西)。

  3. 确保主线程和工作线程之间的同步是正确的。

QT 还有一个叫 QFuture 的东西(不确定 PyQT 是否有),可以用来在主线程中保存新的模型,并在工作线程重新生成模型时自动重新加载。

祝你好运。

撰写回答