Как обновить представление при изменении данных в модели?

1

Я работаю над простым проводником в дереве на основе qtreeview с представлением модели/контроллером/реализацией. Мне нужно использовать некоторые потоки, которые рекурсивно выполняют поиск в подпапках и загружают модель/данные qtreeview. Все это прекрасно. Но моя проблема в том, что представление не обновляется при изменении данных...

Я пробовал несколько разных вещей, но я не доволен ни одним из решений:

QtGui.QStandardItemModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())

Испускание изменения данных из setData() модели должно обновлять представление, но оно не работает для меня. Также я havnt нашел элегантный способ найти qmodelIndex. Я просто возвращаю все циклы, чтобы найти нужный Индекс.

from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import *
from PyQt4.QtCore import *

import time
import traceback, sys, os
from glob import glob
from random import randrange
import traceback

DEPTH = 0
threadpool = QThreadPool()

##########################################
###### Example thread function #####
##########################################
def listFolders( parent ):
    global DEPTH
    time.sleep(2)
    if DEPTH>4:
        return {'fileList':[], 'parent':parent}
    else:
        DEPTH+=1
    fileList = []
    for item in range(randrange(1,5)):
        fileList.append('item_'+str(item))

    return {'fileList':fileList, 'parent':parent}



##########################################
###### simple threading #####
##########################################
class WorkerSignals(QObject):
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    progress = pyqtSignal(int)

class Worker(QRunnable):
    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()
        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

    @pyqtSlot()
    def run(self):
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            self.signals.finished.emit()  # Done


##########################################
###### Model for qtreeview #####
##########################################
class SceneGraphModel(QtCore.QAbstractItemModel):
    def __init__(self, root ,parent=None):
        super(SceneGraphModel, self).__init__(parent)
        self._rootNode = root

    def rowCount(self, parent):
        if not parent.isValid():
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()
        return parentNode.childCount()


    def columnCount(self, parent):
        return 1

    def data(self, index, role):

        if not index.isValid():
            return None

        node = index.internalPointer()

        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            if index.column() == 0:

                return node.name()

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if index.isValid():
            if role == QtCore.Qt.EditRole:
                node = index.internalPointer()
                node.setName(value)
                return True
        return False

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if section == 0:
                return "Scenegraph"

    def flags(self, index):
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable


    def parent(self, index):
        node = self.getNode(index)
        parentNode = node.parent()
        if parentNode == self._rootNode:
            return QtCore.QModelIndex()
        if parentNode == None:
            row = 0
        else:
            row = parentNode.row()
        return self.createIndex(row, 0, parentNode)

    def index(self, row, column, parent):
        parentNode = self.getNode(parent)
        childItem = parentNode.child(row)

        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()


    def getNode(self, index):
        if index.isValid():
            node = index.internalPointer()
            if node:
                return node

        return self._rootNode

##########################################
###### Node class that contain the qtreeview datas #####
##########################################
class Node(object):
    def __init__(self, name, parent=None):
        self._name = name
        self._children = []
        self._parent = parent
        if parent is not None:
            parent.addChild(self)

    def typeInfo(self):
        return "folder"

    def addChild(self, child):
        self._children.append(child)

    def name(self):
        return self._name

    def child(self, row):
        return self._children[row]

    def childCount(self):
        return len(self._children)

    def parent(self):
        return self._parent

    def row(self):
        if self._parent is not None:
            return self._parent._children.index(self)

    def __repr__(self):
        return 'NODE_'+self.name()


##########################################
###### qtreeview containing the threading #####
##########################################
class DirectoryTree(QTreeView):
    def __init__(self):
        super(DirectoryTree, self).__init__()

        #create root node
        self.rootNode   = Node('root')
        #add model to treeview
        self._model = SceneGraphModel(self.rootNode)
        self.setModel(self._model)
        #recurive loop with thread to add more datas
        self.loop( self.rootNode )


    def thread(self, path):
        return listFolders(path)

    def threadResult(self, result ):
        for item in result['fileList']:
            newNode = Node(item,result['parent'])
            self.loop(newNode)


    def loop(self, parent ):
        worker = Worker( self.thread, parent )
        worker.signals.result.connect( self.threadResult )
        threadpool.start(worker)



##########################################
###### window with countdown #####
##########################################

class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.counter = 0
        self.layout = QVBoxLayout()
        self.l = QLabel("Start")
        self.layout.addWidget(self.l)
        w = QWidget()
        w.setLayout(self.layout)
        self.setCentralWidget(w)

        self.treeView = DirectoryTree()
        self.layout.addWidget(self.treeView)

        self.show()

        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.recurring_timer)
        self.timer.start()


        self.setGeometry(0, 0, 650, 550)
        self.setWindowTitle("shot tree")
        self.centerOnScreen()

    def centerOnScreen (self):
        resolution = QtGui.QDesktopWidget().screenGeometry()
        self.move((resolution.width() / 2) - (self.frameSize().width() / 2),
                  (resolution.height() / 2) - (self.frameSize().height() / 2)) 


    def recurring_timer(self):
        self.counter +=1
        self.l.setText("Counter: %d" % self.counter)

        ##### This is a hack to refresh the view
        ##### i want to remove this line 
        ##### and properly emit the changes from the node class to refresh the qtreeview
        self.treeView.expandAll()


app = QApplication([])
window = MainWindow()
app.exec_()

Это мой пример кода. в главном окне будет отсчет, который будет выполняться: self.treeView.expandAll() каждую секунду, чтобы заставить просмотр обновиться, я хочу найти лучшее решение...

Связанные темы я нашел:

Обновить представление, когда данные модели не изменились (Qt/PySide/PyQt)?

PyQt и MVC-шаблон

Теги:
pyqt4
pyqt
qtreeview
qabstractitemmodel

1 ответ

0
Лучший ответ

Проблема не имеет ничего общего с потоками. Для того, чтобы уведомление было уведомлено, модель должна излучать сигнал layoutAboutToBeChanged перед изменением и layoutChanged после изменения, но для этого узел должен получить доступ к модели, поэтому модель должна быть выполнена как атрибут Node. С этим изменением вам больше не нужен QTimer для обновления представления.

class SceneGraphModel(QtCore.QAbstractItemModel):
    def __init__(self, root, parent=None):
        super(SceneGraphModel, self).__init__(parent)
        self._rootNode = root
        self._rootNode._model = self

    def rowCount(self, parent=QtCore.QModelIndex()):
        if not parent.isValid():
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()
        return parentNode.childCount()

    def columnCount(self, parent=QtCore.QModelIndex()):
        return 1

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return None

        node = index.internalPointer()

        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            if index.column() == 0:
                return node.name()

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if index.isValid():
            if role == QtCore.Qt.EditRole:
                node = index.internalPointer()
                node.setName(value)
                return True
        return False

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole:
            if section == 0:
                return "Scenegraph"

    def flags(self, index):
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable

    def parent(self, index):
        node = self.getNode(index)
        parentNode = node.parent()
        if parentNode == self._rootNode:
            return QtCore.QModelIndex()
        if parentNode is None:
            row = 0
        else:
            row = parentNode.row()
        return self.createIndex(row, 0, parentNode)

    def index(self, row, column, parent=QtCore.QModelIndex()):
        parentNode = self.getNode(parent)
        childItem = parentNode.child(row)
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()

    def getNode(self, index):
        if index.isValid():
            node = index.internalPointer()
            if node:
                return node
            print("node", node)
        return self._rootNode


class Node(object):
    def __init__(self, name, parent=None):
        self._name = name
        self._children = []
        self._parent = parent
        self._model = None
        if parent is not None:
            parent.addChild(self)

    def typeInfo(self):
        return "folder"

    def addChild(self, child):
        self._model.layoutAboutToBeChanged.emit()
        self._children.append(child)
        child._model = self._model
        self._model.layoutChanged.emit()

    def name(self):
        return self._name

    def setName(self, name):
        self._name = name

    def child(self, row):
        return self._children[row] if row < len(self._children) else None

    def childCount(self):
        return len(self._children)

    def parent(self):
        return self._parent

    def row(self):
        return 0 if self.parent() is None else self._parent._children.index(self)

    def __repr__(self):
        return 'NODE_' + self.name()
  • 0
    Спасибо! это именно то, чего мне не хватало. Я не знал, как использовать layoutAboutToBeChanged и layoutChanged. Теперь все это имеет смысл!
  • 0
    @nicosalto модель является элементом, независимым от представления, например, модель может быть в нескольких представлениях, поэтому я спрашиваю вас, кто должен уведомлять: модель представлениям или представления модели?
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню