如何使用PYQT QAbstractItemModel正确处理拖放



这是我在TreeView/Model疯狂两天后结束的一段代码。这个主题似乎比我想象的要宽泛得多。我几乎不能花那么多时间来创建一个单独的小部件。无论如何TreeView项的拖放功能已启用。但除了一些有趣的打印输出外,并没有太多内容。双击项目允许用户输入一个不会拾取的新项目名称。

一天后用修订后的代码进行了编辑

它现在是90%的功能工具

用户可以通过拖放、创建/复制/删除和重命名来操作TreeView项目。TreeView项在驱动器上创建目录或文件夹之前,通过点击"打印"按钮(而不是os.makedirs(),该工具仍然只是将每个目录打印为字符串
我对结果很满意。感谢hackyday以及所有回答和帮助我解决问题的人。

最后的祝福。。。

01号愿望:

  1. 我希望PrintOut()方法能使用一个更优雅、更智能的函数来循环浏览TreeView项,以构建一个传递给make_dirs_from_dict()方法的字典

一个愿望号码02:

  1. 我希望删除项目会更稳定。由于某种未知的原因,一个工具在点击第三次/第四次删除按钮时崩溃。到目前为止,我还无法找到问题的根源

一个愿望编号03:3.我祝大家一切顺利,并感谢您的帮助:

import sys, os
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from copy import deepcopy
import cPickle    
class TreeItem(object):
def __init__(self, name, parent=None):
self.name = QtCore.QString(name)       
self.parent = parent
self.children = []       
self.setParent(parent)
def setParent(self, parent):
if parent != None:
self.parent = parent
self.parent.appendChild(self)
else:     self.parent = None
def appendChild(self, child):
self.children.append(child)
def childAtRow(self, row):
if len(self.children)>row: 
return self.children[row]
def rowOfChild(self, child):       
for i, item in enumerate(self.children):
if item == child:  return i
return -1
def removeChild(self, row):
value = self.children[row]
self.children.remove(value)
return True
def __len__(self):
return len(self.children) 
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self):
QtCore.QAbstractItemModel.__init__(self)
self.columns = 1
self.clickedItem=None
self.root = TreeItem('root', None) 
levelA = TreeItem('levelA', self.root)
levelB = TreeItem('levelB', levelA)
levelC1 = TreeItem('levelC1', levelB)
levelC2 = TreeItem('levelC2', levelB)
levelC3 = TreeItem('levelC3', levelB)
levelD = TreeItem('levelD', levelC3)
levelE = TreeItem('levelE', levelD)
levelF = TreeItem('levelF', levelE)
def nodeFromIndex(self, index):
return index.internalPointer() if index.isValid() else self.root
def index(self, row, column, parent):        
node = self.nodeFromIndex(parent)
return self.createIndex(row, column, node.childAtRow(row))
def parent(self, child):
# print 'n parent(child)', child  # PyQt4.QtCore.QModelIndex
if not child.isValid():  return QModelIndex()
node = self.nodeFromIndex(child)       
if node is None:   return QModelIndex()
parent = node.parent           
if parent is None:      return QModelIndex()       
grandparent = parent.parent
if grandparent==None:    return QModelIndex()
row = grandparent.rowOfChild(parent)    
assert row != - 1
return self.createIndex(row, 0, parent)
def rowCount(self, parent):
node = self.nodeFromIndex(parent)
if node is None: return 0
return len(node)
def columnCount(self, parent):
return self.columns
def data(self, index, role):
if role == Qt.DecorationRole:
return QVariant()               
if role == Qt.TextAlignmentRole:
return QVariant(int(Qt.AlignTop | Qt.AlignLeft))       
if role != Qt.DisplayRole:   
return QVariant()                   
node = self.nodeFromIndex(index)       
if index.column() == 0:     
return QVariant(node.name)       
elif index.column() == 1:   
return QVariant(node.state)       
elif index.column() == 2:   
return QVariant(node.description)
else:   return QVariant()
def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction
def flags(self, index):
defaultFlags = QAbstractItemModel.flags(self, index)       
if index.isValid():  return Qt.ItemIsEditable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags           
else:   return Qt.ItemIsDropEnabled | defaultFlags  
def setData(self, index, value, role):
if role == Qt.EditRole:
if value.toString() and len(value.toString())>0: 
self.nodeFromIndex(index).name = value.toString()
self.dataChanged.emit(index, index)
return True
def mimeTypes(self):
return ['bstream', 'text/xml']
def mimeData(self, indexes):
mimedata = QtCore.QMimeData()
bstream = cPickle.dumps(self.nodeFromIndex(indexes[0]))
mimedata.setData('bstream', bstream)
return mimedata
def dropMimeData(self, mimedata, action, row, column, parentIndex):
if action == Qt.IgnoreAction: return True  
droppedNode=cPickle.loads(str(mimedata.data('bstream')))
droppedIndex = self.createIndex(row, column, droppedNode)
parentNode = self.nodeFromIndex(parentIndex)
newNode = deepcopy(droppedNode)
newNode.setParent(parentNode)
self.insertRow(len(parentNode)-1, parentIndex)
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex)
return True
def insertRow(self, row, parent):
return self.insertRows(row, 1, parent)
def insertRows(self, row, count, parent):
self.beginInsertRows(parent, row, (row + (count - 1)))
self.endInsertRows()
return True
def removeRow(self, row, parentIndex):
return self.removeRows(row, 1, parentIndex)
def removeRows(self, row, count, parentIndex):
self.beginRemoveRows(parentIndex, row, row)
node = self.nodeFromIndex(parentIndex)
node.removeChild(row)
self.endRemoveRows()       
return True

class GUI(QtGui.QDialog):
def build(self, myWindow):
myWindow.resize(600, 400)
self.myWidget = QWidget(myWindow)        
self.boxLayout = QtGui.QVBoxLayout(self.myWidget)
self.treeView = QtGui.QTreeView()
self.treeModel = TreeModel()
self.treeView.setModel(self.treeModel)
self.treeView.expandAll()
self.treeView.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.treeView.connect(self.treeView.model(), SIGNAL("dataChanged(QModelIndex,QModelIndex)"), self.onDataChanged)
QtCore.QObject.connect(self.treeView, QtCore.SIGNAL("clicked (QModelIndex)"),  self.treeItemClicked)
self.boxLayout.addWidget(self.treeView)

self.PrintButton= QtGui.QPushButton("Print")  
self.PrintButton.clicked.connect(self.PrintOut) 
self.boxLayout.addWidget(self.PrintButton)
self.DeleteButton= QtGui.QPushButton("Delete")  
self.DeleteButton.clicked.connect(self.DeleteLevel) 
self.boxLayout.addWidget(self.DeleteButton)
self.insertButton= QtGui.QPushButton("Insert")  
self.insertButton.clicked.connect(self.insertLevel) 
self.boxLayout.addWidget(self.insertButton)
self.duplicateButton= QtGui.QPushButton("Duplicate")  
self.duplicateButton.clicked.connect(self.duplicateLevel) 
self.boxLayout.addWidget(self.duplicateButton)
myWindow.setCentralWidget(self.myWidget)

def make_dirs_from_dict(self, dirDict, current_dir='/'):
for key, val in dirDict.items():
#os.mkdir(os.path.join(current_dir, key))
print "tt Creating directory: ", os.path.join(current_dir, key)
if type(val) == dict:
self.make_dirs_from_dict(val, os.path.join(current_dir, key))
def PrintOut(self):
result_dict = {}
for a1 in self.treeView.model().root.children:
result_dict[str(a1.name)]={}
for a2 in a1.children:
result_dict[str(a1.name)][str(a2.name)]={}
for a3 in a2.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)]={}
for a4 in a3.children:
result_dict[ str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)]={}
for a5 in a4.children:
result_dict[ str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)]={}
for a6 in a5.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)][str(a6.name)]={}
for a7 in a6.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)][str(a6.name)][str(a7.name)]={}

self.make_dirs_from_dict(result_dict)                      

def DeleteLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentRow=currentIndex.row()
currentColumn=currentIndex.column()
currentNode = currentIndex.internalPointer()
parentNode = currentNode.parent
parentIndex = self.treeView.model().createIndex(currentRow, currentColumn, parentNode)
print 'nttt CurrentNode:', currentNode.name, ', ParentNode:', currentNode.parent.name, ', currentColumn:', currentColumn, ', currentRow:', currentRow 
# self.treeView.model().removeRow(len(parentNode)-1, parentIndex) 
self.treeView.model().removeRows(currentRow, 1, parentIndex )
#self.treeView.model().removeRow(len(parentNode), parentIndex)
#self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex) 
def insertLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentNode = currentIndex.internalPointer()
newItem = TreeItem('Brand New', currentNode)
self.treeView.model().insertRow(len(currentNode)-1, currentIndex)
self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), currentIndex, currentIndex)   
def duplicateLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentRow=currentIndex.row()
currentColumn=currentIndex.column()
currentNode=currentIndex.internalPointer()
parentNode=currentNode.parent
parentIndex=self.treeView.model().createIndex(currentRow, currentColumn, parentNode)
parentRow=parentIndex.row()
parentColumn=parentIndex.column()
newNode = deepcopy(currentNode)
newNode.setParent(parentNode)
self.treeView.model().insertRow(len(parentNode)-1, parentIndex)
self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex) 
print 'nttt CurrentNode:', currentNode.name, ', ParentNode:', parentNode.name, ', currentColumn:', currentColumn, ', currentRow:', currentRow, ', parentColumn:', parentColumn, ', parentRow:', parentRow 
self.treeView.update()
self.treeView.expandAll()

def treeItemClicked(self, index):
print "n clicked item ----------->", index.internalPointer().name
def onDataChanged(self, indexA, indexB):
print "n onDataChanged NEVER TRIGGERED! ####################### n ", index.internalPointer().name
self.treeView.update(indexA)
self.treeView.expandAll()
self.treeView.expanded()

if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
myWindow = QMainWindow()
myGui = GUI()
myGui.build(myWindow)
myWindow.show()
sys.exit(app.exec_())

我不完全确定你想要实现什么,但听起来你想在拖放操作中检索拖动的项目,并双击保存一个新的节点名称。

首先,您需要将拖动的项目保存到mimeData中。目前,您只保存字符串"mimeData",它不会告诉您太多信息。它被保存为的mimeType字符串(这里我使用了"bstream")实际上可以是任何东西。只要它与用于检索数据的内容相匹配,并且在模型的mimeTypes方法返回的列表中。要传递对象本身,必须首先对其进行序列化(如果您正计划将对象转换为xml,也可以将其转换为xml),因为它不是mime数据的标准类型。

为了保存输入的数据,必须重新实现模型的setData方法并定义EditRole的行为。

相关方法:

def setData(self, index, value, role):
if role == Qt.EditRole:
self.nodeFromIndex(index).name = value
self.dataChanged.emit(index, index)
return True
def mimeTypes(self):
return ['bstream', 'text/xml']
def mimeData(self, indexes):
mimedata = QtCore.QMimeData()
# assuming single dragged item ...
# only pass the node name
# mimedata.setData('text/xml', str(self.nodeFromIndex(indexes[0]).name))
# pass the entire object
bstream = cPickle.dumps(self.nodeFromIndex(indexes[0]))
mimedata.setData('bstream', bstream)
return mimedata
def dropMimeData(self, mimedata, action, row, column, parentIndex):
if action == Qt.IgnoreAction: return True        
parentNode = self.nodeFromIndex(parentIndex)
# data = mimedata.data('text/xml')
data = cPickle.loads(str(mimedata.data('bstream')))
print 'nt incoming row number:', row, ', incoming column:', column, 
', action:', action, ' mimedata: ', data.name
print "nt Item's name on which drop occurred: ", parentNode.name, 
', number of its childred:', len(parentNode.children)
if len(parentNode.children)>0: print 'nt zero indexed child:', parentNode.children[0].name
return True

编辑:
这是你更新的很多代码,但我会遵守你强调的要点。避免在模型类之外调用createIndex。这是Qt中受保护的方法;Python不强制执行私有/受保护的变量或方法,但当使用另一种语言的库时,我会尽量尊重类的预期组织和对它们的访问。

该模型的目的是为您的数据提供一个接口。您应该使用模型的indexdataparent等公共函数来访问它。要获取给定索引的父级,请使用该索引(或模型)的parent函数,该函数还将返回QModelIndex。这样,您就不必浏览(或者确实了解)数据的内部结构。这就是我在deleteLevel方法中所做的。

来自qt文档:

为了确保数据的表示与访问方式分离,引入了模型索引的概念。可以通过模型获得的每一条信息都由模型索引表示。。。只有模型需要知道如何获取数据,并且可以相当普遍地定义模型管理的数据类型。

此外,您可以使用递归来简化打印方法。

def printOut(self):
result_dict = dictify(self.treeView.model().root)
self.make_dirs_from_dict(result_dict)  
def deleteLevel(self):
if len(self.treeView.selectedIndexes()) == 0: 
return
currentIndex = self.treeView.selectedIndexes()[0]
self.treeView.model().removeRow(currentIndex.row(), currentIndex.parent())

我有这个独立于课堂

def dictify(node):
kids = {}
for child in node.children:
kids.update(dictify(child)) 
return {str(node.name): kids}

相关内容

  • 没有找到相关文章

最新更新