如何在模型中数据更改时刷新视图?
how to refresh the view when data changes in model?
我正在开发一个简单的树目录浏览器,它基于带有模型 view/controller/implementation 的 qtreeview。我需要使用一些递归搜索子文件夹并提供 qtreeview 的 model/datas 的线程。所有这一切都很好。
但我的问题是当数据更改时视图不会刷新...
我尝试了一些不同的方法,但我对任何解决方案都不满意:
QtGui.QStandardItemModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
从模型的 setData() 发出数据更改应该更新视图,但它对我不起作用。我还没有找到一种查找 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_()
这是我的代码示例。主要 window 中有一个倒计时将执行:self.treeView.expandAll()
每秒强制视图更新,我想找到更好的解决方案...
我找到的相关主题:
PyQt and MVC-pattern
问题与线程无关。对于要通知的视图,模型必须在更改前发出信号 layoutAboutToBeChanged
,在更改后发出信号 layoutChanged
,但为此节点必须访问模型,因此模型必须作为节点属性.通过该更改,您不再需要 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()
我正在开发一个简单的树目录浏览器,它基于带有模型 view/controller/implementation 的 qtreeview。我需要使用一些递归搜索子文件夹并提供 qtreeview 的 model/datas 的线程。所有这一切都很好。 但我的问题是当数据更改时视图不会刷新...
我尝试了一些不同的方法,但我对任何解决方案都不满意:
QtGui.QStandardItemModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
从模型的 setData() 发出数据更改应该更新视图,但它对我不起作用。我还没有找到一种查找 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_()
这是我的代码示例。主要 window 中有一个倒计时将执行:self.treeView.expandAll() 每秒强制视图更新,我想找到更好的解决方案...
我找到的相关主题:
PyQt and MVC-pattern
问题与线程无关。对于要通知的视图,模型必须在更改前发出信号 layoutAboutToBeChanged
,在更改后发出信号 layoutChanged
,但为此节点必须访问模型,因此模型必须作为节点属性.通过该更改,您不再需要 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()