Как обновить QStandartItemModel без зависания основного интерфейса - PullRequest
1 голос
/ 13 ноября 2011

Я начинаю изучать PyQt4, давно застрял на чем-то и сам не могу понять:

Вот концепция: существует TreeView с пользовательским QStandartItemModel, который перестраивается каждые пару секунд и может иметь много (не менее сотен) записей, также будут дополнительные делегаты для различных столбцов и т. Д. довольно сложный, и время построения даже простой модели без делегатов достигает 0,3 секунды, что заставляет TreeView зависать.

Пожалуйста, посоветуйте мне лучший подход к решению этой проблемы. Я думал о том, чтобы как-то построить модель в другом потоке и, в конце концов, отправить ее в TreeView, где он просто выполнит 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_()

Спасибо за советы. Вот ситуация, которую я получил до сих пор: При каждом обновлении я строю новую модель и отправляю ее в TreeView ... это быстро, но я не знаю, что происходит с текущей моделью TreeView и как с ней работать, также кажется, что память используется моим «приложением» постоянно увеличивается.

Другое дело, что я хочу сохранить свой выбор, но на основе данных элемента, а не визуального прямоугольника или порядка строк, поэтому я тоже это сделал, но он выглядит слишком грязным / хакерским, чтобы быть правильным. Любая помощь в этом также будет оценена. Код следует:

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 голос
/ 14 ноября 2011

У вас правильная идея.

Вам нужен отдельный рабочий поток для пересчета модели для вас (так часто, как вы хотите, на основе времени или сигнала).Затем вам нужно подключить сигнал, когда вычисления выполнены для уведомления основного потока.

Есть несколько ошибок, о которых вам нужно знать.

  1. QObjectsжить в темах.Парадигма сигнал / слот (по крайней мере в C ++ QT) по умолчанию работает локально для потока владельца.Если вы хотите отправить поперечный поток сигнала, вам нужно указать его явно (см. Документацию по сигналу / соединению).

  2. Чтобы работать с моделью в рабочем потоке, вам необходимо "переместить «модель в рабочий поток (должен быть метод с именем movetothread или что-то в этом роде).

  3. Убедитесь, что основной поток и рабочий поток правильно синхронизированы.

QT также имеет QFuture (не уверен, есть ли у PyQT его), который можно использовать для хранения новой модели в главном потоке и автоматической перезагрузки, когда рабочий поток ее регенерирует.

Удачи.

...