PyQt4:在QTreeView中拖放

2 投票
1 回答
3053 浏览
提问于 2025-04-17 07:51

我正在用PyQt4制作一个用户界面,其中有一个树形视图(treeView),我想对它进行一些操作。这个树形视图是基于模型的。我在一个.py文件中创建了一些数据并导入到这个界面中。这样,我就能在树形视图中看到这些数据了。

不过,我现在无法拖放这些数据,所以也就无法改变它们的顺序。我参考了一些文章,试着把相关的代码加到我的脚本里,但都没有成功。我在代码里加了一些“打印”(print)语句,以便追踪问题。

我发现,当我拖动一个项目时,它会转换成MIME数据(这是一种数据格式)。但是当我放下这个项目时,我没有看到任何输出。看起来我的脚本没有调用“dropMimeData”这个方法。我该如何修复我的脚本呢?

from PyQt4 import QtCore, QtGui
from setting import *
from copy import deepcopy
from cPickle import dumps, load, loads
from cStringIO import StringIO

class PyMimeData(QtCore.QMimeData):
    MIME_TYPE = QtCore.QString('text/plain')

    def __init__(self, data=None):
        QtCore.QMimeData.__init__(self)

        self._local_instance = data

        if data is not None:
            try:
                pdata = dumps(data)
            except:
                return

            self.setData(self.MIME_TYPE, dumps(data.__class__) + pdata)

    @classmethod
    def coerce(cls, md):
        if isinstance(md, cls):
            return md
        if not md.hasFormat(cls.MIME_TYPE):
            return None
        nmd = cls()
        nmd.setData(cls.MIME_TYPE, md.data())

        return nmd

    def instance(self):
        if self._local_instance is not None:
            return self._local_instance

        io = StringIO(str(self.data(self.MIME_TYPE)))

        try:
            load(io)
            return load(io)
        except:
            pass

        return None

    def instanceType(self):
        if self._local_instance is not None:
            return self._local_instance.__class__

        try:
            return loads(str(self.data(self.MIME_TYPE)))
        except:
            pass
        return None

class treeItem(QtGui.QStandardItem):
    def __init__(self, data, parent=None):
        super(treeItem, self).__init__(data)
        self.parentItem = parent
        self.itemData = data
        self.childItems = []

    def appendChild(self, item):
        self.childItems.append(item)

    def parent(self):
        return self.parentItem

    def childAtRow(self, row): 
        return self.childItems[row]

    def rowOfChild(self, child):       
        for i, item in enumerate(self.childItems): 
            if item == child: 
                return i 
        return -1 


class treeModel(QtGui.QStandardItemModel):
    def __init__(self, name, parent=None):
        super(treeModel, self).__init__(parent)
        self.headerName = name
        self.childItems = []

    def appendChild(self, item):
        self.childItems.append(item)

    def removeRowAll(self):
        pass

    def addItemList(self, parent, elements):
        for text, children in elements:
            item = treeItem(text, parent)
            self.addItems(parent, item)

            if children:
                self.addItemList(item, children)

    def addItems(self, parent, inputItem):
        parent.appendRow(inputItem)
        parent.appendChild(inputItem)

    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self.headerName


    def supportedDropActions(self):
        return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction

    def flags(self, index): 
        defaultFlags = QtCore.QAbstractItemModel.flags(self, index) 

        if index.isValid():    
            return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | defaultFlags 

        else:     
            return QtCore.Qt.ItemIsDropEnabled | defaultFlags 

    def mimeTypes(self): 
        types = QtCore.QStringList() 
        types.append('text/plain') 
        return types 

    def mimeData(self, index):
        node = self.nodeFromIndex(index[0])
        mimeData = PyMimeData(node)        
        return mimeData

    def dropMimeData(self, mimedata, action, row, column, parentIndex):
        print mimedata, action, row, column, parentIndex
        if action == QtCore.Qt.IgnoreAction:
            return True

        dragNode = mimedata.instance()
        print dragNode
        parentNode = self.nodeFromIndex(parentIndex)

        # copy of node being moved
        newNode = deepcopy(dragNode)
        print newNode
        newNode.setParent(parentNode)
        self.insertRow(len(parentNode)-1, parentIndex)
        self.emit(QtCore.SIGNAL("dataChanged(QtCore.QModelIndex,QtCore.QModelIndex)"), parentIndex, parentIndex)
        return True

def nodeFromIndex(self, index):        
    ##return index.internalPointer() if index.isValid() else self.root        
    return index.model() if index.isValid() else self.parent()

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 RigControlWindow(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self, parent = getMayaWindow()):
        super(RigControlWindow, self).__init__(parent)
        self.setupUi(self)

        self.bodyrig_treelist.setDragEnabled(1)
        self.bodyrig_treelist.setAcceptDrops(1)
        self.bodyrig_treelist.setDropIndicatorShown(1)
        self.bodyrig_treelist.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
        QtCore.QObject.connect(self.finalize_button, QtCore.SIGNAL("clicked()"), self.AddData_treeList)

    def AddData_treeList(self):
        self.localtreeModel = treeModel("objects")
        self.bodyrig_treelist.setModel(self.localtreeModel)
        self.localtreeModel.addItemList(self.localtreeModel, data)

数据是

data = [("root",[("upper",[("hand",[]),
                           ("head",[])
                           ]),
                 ("lower",[("leg",[]),
                           ("foot",[])
                           ])
                 ])
        ]

1 个回答

2

QTreeView.dragMoveEventQTreeView.dragEnterEvent 这两个方法会检查通过 event.mimeData() 获取的对象,看看它是否能提供模型支持的任何格式的数据(也就是通过 model.mimeTypes() 返回的格式)。

但是你的 PyMimeData 子类并不支持任何格式,因为它从来没有成功设置传给它构造函数的数据。

问题出在 PyMimeData.__init__ 方法里:

...
try:
    pdata = dumps(data)
except:
    return
self.setData(self.MIME_TYPE, dumps(data.__class__) + pdata)

这里的 data 是从 treeModel.mimeData 方法传过来的:

def mimeData(self, index):
    node = self.nodeFromIndex(index[0])
    mimeData = PyMimeData(node)
    return mimeData

但是如果你检查一下 data/node 的类型,你会发现它是一个 treeModel 实例,因此 dumps(data) 会失败,因为 data 不能被序列化(也就是不能被“打包”)。结果就是 PyMimeData 对象没有被正确初始化,所以在拖拽事件中会被忽略。

撰写回答