在QAbstractItemModel PySide PyQt中的拖放

问题描述 投票:4回答:2

[我一直在努力学习如何使用QTreeView和自定义Item类实现QAbstractItemModel,并且除了拖放操作之外,我都能正常工作。

最终,我希望能够使用shift键在“移动”和“复制”项目之间进行切换,但是现在,我只是试图让InternalMove完全起作用。...

我正在像这样重新实现mimeData和dropMimeData。...

class BuildModel( QAbstractItemModel ):
    def __init__( self, root):
        super( BuildModel, self ).__init__()

    def mimeTypes( self ):
        return ['sushi-build-items']

    def mimeData( self, indices ):
        mimedata = QMimeData()
        mimedata.setData('sushi-build-items', self.getSerializedData(indices) )
        return mimedata

    def dropMimeData( self, mimedata, action, row, column, parentIndex ):
        if not mimedata.hasFormat( 'sushi-build-items' ):
            return False
        data = pickle.loads((str(mimedata.data('sushi-build-items'))))
        items = dataToItems(data)
        self.insertItems(row, items, parentIndex)
        return True

    def insertItems( self, row, items, parentIndex):
        parent = self.itemFromIndex(parentIndex)
        self.beginInsertRows( parentIndex, row, row+len(items)-1 )
        if row == -1:
            parent.addChildren(items)
        else:
            parent.insertChildren(row, items)
        self.endInsertRows()
        self.dataChanged.emit(parentIndex, parentIndex)
        return True

而且我的树状视图设置为这样的InternalMove。...

class TreeView(QTreeView):
    def __init__(self, parent = None, model = None):

        super(TreeView, self).__init__(parent = parent)
        self.setDragDropMode(QAbstractItemView.InternalMove)
        self.setDragEnabled(True)
        self.setAcceptDrops(True)

但是当我拖放源项目时,它保持原样,它只是删除重复的项目。TreeView不应该处理所拖动项目吗?如果没有,我该在哪里手动删除它?

我确信我在这里缺少什么。。

python qt pyqt pyside
2个回答
3
投票

您需要在模型中实现removeRows方法;它应该会自动被调用。


0
投票

这里是可执行代码。

Disclaimer:您的代码有时与我的代码不同,我喜欢您的编码风格。这是一个琐碎的,仅可执行的代码。

[dropMimeData返回bool

True表示您在removeRows之后自动使用insertRows。(拖放我认为它将绝对使用insertRows ...)False表示您不使用它。

有时自动使用removeRows不方便。

例如,如果在其上拖放多个项目,则如果在True中返回dropMimeData,通常位置不准确。(position参数增加的越多,子代的长度越减少。这取决于您的编码...)

在singleSelection中,您可能应该返回True

除此以外,QAbstractItemModel具有removeRow。最终返回removeRows(row,1,parent)。因此removeRows的内容最终决定了删除的方式。

这里是C++的代码

inline bool QAbstractItemModel :: removeRow(int arow,const QModelIndex&aparent){return removeRows(arow,1,aparent); }

removeRows,您需要重写并定义自己的方式。

bool QAbstractItemModel :: removeRows(int,int,const QModelIndex&){返回false;}

from PySide import QtGui
from PySide import QtCore
import pickle
import sys


class TreeView(QtGui.QTreeView):
    def __init__(self, parent = None, model = None):

        super(TreeView, self).__init__(parent = parent)
        self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
        self.setDragEnabled(True)
        self.setAcceptDrops(True)

class BuildModel( QtCore.QAbstractItemModel ):
    def __init__( self, items, parent):
        super( BuildModel, self ).__init__()
        self.__parent = parent
        self.root = TreeItem(["root", 0], None)        

        if items is not None:
            for i in items:
                self.root.addChild(i)   
    def rowCount(self, parent=QtCore.QModelIndex()):
        if not parent.isValid():
            r = self.root   
            return r.childCount()
        else:
            r = parent.internalPointer()            
            return r.childCount()
    def columnCount(self, parent=QtCore.QModelIndex()):
        return 2
    def flags(self, index):         
        return QtCore.Qt.ItemFlag.ItemIsDragEnabled|QtCore.Qt.ItemIsDropEnabled|QtCore.Qt.ItemIsEditable|QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled        

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):

        if orientation == QtCore.Qt.Orientation.Horizontal:
            if role == QtCore.Qt.DisplayRole:
                if section == 0:
                    return "Country"
                elif section == 1:
                    return "CountryID"   
    def index(self, row, column , _parent = QtCore.QModelIndex()):
        if not _parent.isValid():
            parent = self.root
        else:
            parent = _parent.internalPointer()
        if not self.hasIndex(row, column, _parent):
            return QtCore.QModelIndex()
        child = parent.child(row)
        if child:
            childIndex = self.createIndex(row, column, child)            
            childIndex = QtCore.QPersistentModelIndex(childIndex)

            return childIndex
        else:
            return QtCore.QModelIndex()
    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return None
        treeitem = index.internalPointer()        
        if role == QtCore.Qt.DisplayRole:
            column = index.column()            
            return treeitem._itemData[column]
        if role == QtCore.Qt.DecorationRole:
            column = index.column()
            if column == 0:
                return treeitem.icon   
    def parent(self, child):
        if not child.isValid():
            return QtCore.QModelIndex()        
        treeitem = child.internalPointer()        
        parent = treeitem.parent()           
        if parent is not None:
            parentindex =self.createIndex(treeitem.at(), 0, parent)    
            return parentindex
        else:
            return QtCore.QModelIndex()
    def supportedDragActions(self):        
        return  QtCore.Qt.MoveAction
    def supportedDropActions(self):
        return QtCore.Qt.MoveAction
    def mimeTypes( self ):
        return ['sushi-build-items']

    def mimeData( self, indices ):
        mimedata = QtCore.QMimeData()
        mimedata.setData('sushi-build-items', self.getSerializedData(indices) )
        return mimedata

    def dropMimeData( self, data, action, row, column, parent):
        if not data.hasFormat( 'sushi-build-items' ):
            return False
        byteArray = QtCore.QByteArray(data.data(('sushi-build-items')))
        out = QtCore.QDataStream(byteArray , QtCore.QIODevice.ReadOnly)     
        newItems = [] 
        if  self.__parent.selectionMode() ==  QtGui.QTreeView.SelectionMode.SingleSelection:
            indicator = False        
            if row != -1:                
                beginRow = row 
                indicator = True
                parentItem = parent.internalPointer()          
            elif self.__parent.dropIndicatorPosition() == QtGui.QAbstractItemView.OnItem:  
                beginRow = 0         
                parentItem = parent.internalPointer()                
            else:
                beginRow = self.rowCount(parent)   
                indicator = True            
                parentItem = parent.internalPointer()                   
            rows = 0     
            while not out.atEnd():
                text = out.readQStringList()
                icon = QtGui.QIcon()
                out >> icon
                newItem = TreeItem(text, icon, parentItem)        

                newItems.append(newItem)            
                rows += 1        
            startRow = beginRow      
            self.insertRows(beginRow, len(newItems), parent, newItems)   
            beginRow = startRow
            for i in newItems:                               
                idx = self.index(beginRow, 0, parent)    
                self.setData(idx, i)
                beginRow += 1  

        return True
    def getSerializedData(self, indexes):
        blob = QtCore.QByteArray()
        out = QtCore.QDataStream(blob, QtCore.QIODevice.WriteOnly)       
        selectedColumnZeroIndexes = [i for i in indexes if i.column() == 0]      
        for i in selectedColumnZeroIndexes:
            p = i.internalPointer()
            stringList = [str(k) for k in p._itemData]        
            out.writeQStringList(stringList)
            out << p.icon
        return blob
    def insertRows( self, row, count, parent , items):


        if not parent.isValid():
            parentitem = self.root
        else:            
            parentitem = parent.internalPointer()     
        self.beginInsertRows( parent, row, row+ count-1 )
        for i in range(0, count):                               
            item = items[i]
            item.setParent(parentitem)           
            success = parentitem.insertChild(row+i, item)          
        self.emit(QtCore.SIGNAL("dataChanged(QModelIndex*, QModelIndex*)"))
        self.emit(QtCore.SIGNAL("layoutChanged()"))
        self.endInsertRows()
        self.dataChanged.emit(parent, parent)
        return True   
    def removeRows(self, position, count = 1, parent=QtCore.QModelIndex()):

        success = False        
        if not parent.isValid():
            parentitem = self.root
        else:       
            parentitem = parent.internalPointer()          
        items = parentitem.children[position: position + count]   
        if items:            
            self.beginRemoveRows(parent, position, position + count - 1)
            success = parentitem.removeChildren(position, count)        
            self.endRemoveRows()
            self.emit(QtCore.SIGNAL("dataChanged(QModelIndex*, QModelIndex*)"))
            self.emit(QtCore.SIGNAL("layoutChanged()"))      

        self.beginResetModel()
        self.endResetModel()
        return success
    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if role == QtCore.Qt.EditRole:                    
            if isinstance(value, TreeItem):                
                item = self.nodeFromIndex(index.parent())    
                item.children[index.row()]._data = value._data
                item.children[index.row()]._itemData = value._itemData    
                item.children[index.row()].icon = value.icon
                self.dataChanged.emit(index, index)         
                return True
            else:              
                item = self.nodeFromIndex(index.parent())            
                item.children[index.row()]._itemData[index.column()] = value       
                self.dataChanged.emit(index, index)
                return True
            return False
        elif role == QtCore.Qt.DisplayRole:                    
            if isinstance(value, TreeItem):                
                item = self.nodeFromIndex(index.parent())    
                item.children[index.row()]._itemData = value._data   
                item.children[index.row()]._itemData = value._itemData    
                item.children[index.row()].icon = value.icon
                self.dataChanged.emit(index, index)         
                return True
            else:              
                item = self.nodeFromIndex(index.parent())
                item.children[index.row()]._itemData[index.column()] = value    
                self.dataChanged.emit(index, index)
                return True
            return False
        return False
    def nodeFromIndex(self, index):        
        return index.internalPointer() if index.isValid() else self.root
class TreeItem(object):
    def __init__(self, data, icon = QtGui.QIcon(), parent=None):
        super(TreeItem, self).__init__()
        self._data = data    
        self._itemData = data
        self.icon = icon
        self.children = []
        self.row = 0
        self._parent = parent
        self._parentid = -1
        if parent is not None:

            self._parentid = int(self._parent._itemData[1])

    def at(self):
        if self._parent is not None:
            if self in self._parent.children:                
                index = self._parent.children.index(self)
                return index
        return 0
    def child(self, row):
        if row < 0  or row > len(self.children)-1:
            return None

        child = self.children[row]

        return child
    def childCount(self):
        return len(self.children)
    def addChild(self, item):        
        item.setParent(self)       
        self.children.append(item)
        return True
    def columnCount(self):
        return len(self._itemData)
    def data(self, column):
        if column < 0  or column >= self.columnCount():
            return None
        return self._itemData[column]
    def setData(self, column, value):        
        if column < 0  or column >= self.columnCount():
            return None
        self._itemData[column] = value
        return True
    def insertColumns(self, position, columns):
        if position < 0  or position >= self.columnCount():
            return False
        for column in range(0, columns):
            self._itemData.insert(position, None)
        for i in self.children:
            i.insert(position, columns)  
    def insertChild(self, insertrow, item):
        if item in self.children:
            return False
        if insertrow == -1:
            item.setParent(self)
            self.children.append(item)
            return True
        elif len(self.children)  > insertrow:     
            item.setParent(self)
            self.children.insert(insertrow, item)            
            return True
        elif len(self.children)  <= insertrow:     
            item.setParent(self)
            self.children.append(item)   
            return True        
        else:
            return False
    def insertChildren(self, position, lastposition, data):
        if position < 0  or lastposition >= self.columnCount():
            return False
        for i in range(0, lastposition):
            if data in self.children:
                return False
            data.setParent(self)
            self.children.insert(position + i, data)
        return True    
    def addChildren(self, items):
        self.children.extend(items)
        return True
    def removeChild(self, item):
        if item in self.children:
            self.children.remove(item)
            return True
        else:
            return False
    def removeChildren(self, position, count):
        if position < 0  or  position + count > len(self.children):
            return False
        removedItems = self.children[position: position + count]
        for i in removedItems:
            assert(i in removedItems)
            self.children.remove(i)
        return True
    def setParent(self, parent):
        self._parent = parent
        self._parentid = parent._itemData[1]
    def parent(self):
        return self._parent
    def movedItems(self, row, lastRow):
        if (0 < self.childCount() or self.childCount() > lastRow) and row != lastRow:            
            movedItems = self.children[row : lastRow]
            return movedItems
        if row == lastRow:
            print(self.children)
            movedItems = self.children[row]
            return [movedItems]
        return []
def main():
    if QtGui.QApplication.instance() is not None:
        app = QtGui.QApplication.instance()
    else:
        app = QtGui.QApplication([])
    items = [TreeItem(["Peru", 1], QtGui.QIcon(), None), \
                 TreeItem(["Japan", 2], QtGui.QIcon(), None), \
                 TreeItem(["France", 3], QtGui.QIcon(), None), \
                 TreeItem(["England", 4], QtGui.QIcon(), None), \
                 TreeItem(["Germany", 5], QtGui.QIcon(), None), \
                 TreeItem(["Spain", 6], QtGui.QIcon(), None), \
                 TreeItem(["Canada", 7], QtGui.QIcon(), None), \
                 TreeItem(["China", 8], QtGui.QIcon(), None), \
                 TreeItem(["Egypt", 9], QtGui.QIcon(), None), \
                 TreeItem(["Estonia", 10], QtGui.QIcon(), None),\
                 TreeItem(["Finland", 11], QtGui.QIcon(), None),\
                 TreeItem(["Brazil", 12], QtGui.QIcon(), None),\
                 TreeItem(["Batwa", 13], QtGui.QIcon(), None),\
                 TreeItem(["Angola", 14], QtGui.QIcon(), None),\
                 TreeItem(["Andorra", 15], QtGui.QIcon(), None),\
                 TreeItem(["Chad", 16], QtGui.QIcon(), None)]
    view = TreeView()
    model = BuildModel(items, view)
    view.setModel(model)
    view.show()
    sys.exit(QtGui.QApplication.exec_())
if __name__ == "__main__":
    main()    
© www.soinside.com 2019 - 2024. All rights reserved.