我正在基于模型视图/控制器/实现基于qtreeview的简单树目录浏览器上工作。我需要使用一些线程来递归搜索子文件夹并提供qtreeview的模型/数据。所有这些都很好。 但是我的问题是,当数据更改时,视图不会刷新...
我尝试了几种不同的方法,但是我对任何解决方案都不满意:
QtGui.QStandardItemModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
从模型的setData()发出数据更改应更新视图,但对我不起作用。我也找到了一种找到qmodelIndex的优雅方法。 Iam只是回想起地循环所有数据以找到正确的索引。
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() 每秒强制视图更新,我想找到一个更好的解决方案...
我发现的相关主题:
Refresh view when model data has not changed (Qt/PySide/PyQt)?
答案 0 :(得分: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()