Я начинаю изучать 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
ntashev 13 Ноя 2011 в 13:41

1 ответ

Вы правильно поняли.

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

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

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

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

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

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

Удачи.

1
enticedwanderer 14 Ноя 2011 в 16:31