Controlling drag-n-drop disable/enable of QTreeWid

2019-09-03 09:24发布

问题:

In my example I want users to have the ability to drag and drop children between families of all kinds. However I want to restrict users from having the ability to drag and drop a person onto another person, or dropping a person as a root object which in return would make it become a family.

I've gotten very close. There is a single bug though. If a user drags a person to a family, while hovering over the cursor over the family, if you carefully move the cursor to the top most edge of the family you'll see the indicator allowing you to drop the item so it's above the Family, not inside of Family. The code tests true and allows the user to successfully drop the person. The result places the person outside of the family. The image below shows what i mean.

The main focus on the code is between lines 75 - 125.

# Imports
# ------------------------------------------------------------------------------
import sys
from PySide import QtCore, QtGui

# Class Object
# ------------------------------------------------------------------------------
class Person():
    def __init__(self, name="", age=0):
        self.name = name
        self.age = age

class Family():
    def __init__(self, name=""):
        self.name = name


DROP_VALIDATION_DICT = { 
    Person : ( Family, None ),
    Family : ( None, ),
}

# Custom QTreeWidgetItem
# ------------------------------------------------------------------------------
class CustomTreeNode( QtGui.QTreeWidgetItem ): 
    def __init__( self, parent, data ):
        super( CustomTreeNode, self ).__init__( parent )
        self.data = data

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, value):
        self._data = value
        self.setText( 0, self.data.name )

    def update(self):
        if self.data:
            self.setText(0, self.data.name)


# Custom QTreeWidgetItem
# ------------------------------------------------------------------------------
class CustomTreeWidget( QtGui.QTreeWidget ):
    def __init__(self, parent=None):
        QtGui.QTreeWidget.__init__(self, parent)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setItemsExpandable(True)
        self.setAnimated(True)
        self.setDragEnabled(True)
        self.setDropIndicatorShown(True)
        self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
        self.setAlternatingRowColors(True)
        # self._dragroot = self.itemRootIndex() # added

        # signals
        self.itemExpanded.connect(self.on_item_expanded)
        self.itemCollapsed.connect(self.on_item_collapsed)

    def set_headers(self, headers=[]):
        self.setColumnCount( len(headers) )
        self.setHeaderLabels( headers )

    def keyPressEvent(self, event):
        if (event.key() == QtCore.Qt.Key_Escape and
            event.modifiers() == QtCore.Qt.NoModifier):
            self.clearSelection()
        else:
            QtGui.QTreeWidget.keyPressEvent(self, event)

    def mousePressEvent(self, event):
        item = self.itemAt(event.pos())
        if item is None:
            self.clearSelection()

        QtGui.QTreeWidget.mousePressEvent(self, event)

    def on_item_expanded(self):
        key_mod = QtGui.QApplication.keyboardModifiers()

        if key_mod == QtCore.Qt.ControlModifier:
            self.expandAll()

    def on_item_collapsed(self):
        key_mod = QtGui.QApplication.keyboardModifiers()

        if key_mod == QtCore.Qt.ControlModifier:
            self.collapseAll()

    # drag-n-drop with constraints
    def dragEnterEvent(self, event):
        item = self.itemAt(event.pos())
        if item is not None and ( isinstance(item.data, Family) ):
        # if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            super(CustomTreeWidget, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        print "testing"
        print "moving..."
        item = self.itemAt(event.pos())
        rect = self.visualItemRect(item)
        # QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.ForbiddenCursor))

        print rect, event.pos()
        print "Cursor: ", event.pos().y(), event.pos().y()+2
        if event.pos().y() < rect.y()+2:
            print "not inside"
        else:
            print "inside"
        super(CustomTreeWidget, self).dragMoveEvent(event)

    def dropEvent(self, event):
        item = self.itemAt(event.pos())
        if item is not None and ( isinstance(item.data, Family) ):
            super(CustomTreeWidget,self).dropEvent(event)

        else:
            print "ignored"
            event.setDropAction(QtCore.Qt.IgnoreAction)


# UI
# ------------------------------------------------------------------------------
class ExampleWidget(QtGui.QWidget):

    def __init__( self, parent=None ):
        super(ExampleWidget, self).__init__()
        self.initUI()


    def initUI(self):

        # widgets
        self.treewidget = CustomTreeWidget()
        self.treewidget.set_headers( ["items"] )
        self.btn_add_family_tree_node = QtGui.QPushButton("Add Family")
        self.btn_add_person_tree_node = QtGui.QPushButton("Add Person")

        # layout
        self.mainLayout = QtGui.QGridLayout(self)
        self.mainLayout.addWidget(self.btn_add_family_tree_node, 0,0)
        self.mainLayout.addWidget(self.btn_add_person_tree_node, 0,1)
        self.mainLayout.addWidget(self.treewidget, 1,0,1,3)

        # signals
        self.btn_add_family_tree_node.clicked.connect(self.add_family_tree_node_clicked)
        self.btn_add_person_tree_node.clicked.connect(self.add_person_tree_node_clicked)

        # display
        self.resize(300, 400)
        self.show()
        self.center_window(self, True)

        # test data
        self.add_test_families("Roberstons", ["Kevin", "Mindy", "Riley"])
        self.add_test_families("Rodriguez", ["Matt", "Kim", "Stephanie"])

    def add_test_families(self, name, children):
        family = Family( name )
        node = CustomTreeNode( self.treewidget, family )
        node.setExpanded(True)

        # # disable drag and drop flags
        # # QtCore.Qt.ItemIsDragEnabled
        # flags =  QtCore.Qt.ItemIsSelectable | \
        #     QtCore.Qt.ItemIsUserCheckable | \
        #     QtCore.Qt.ItemIsEnabled | \
        #     QtCore.Qt.ItemIsDropEnabled 
        # node.setFlags(flags)

        for i in xrange(len(children)):
            person = Person( children[i] )
            CustomTreeNode( node, person )


    # Functions
    # --------------------------------------------------------------------------
    def add_family_tree_node_clicked(self):
        print "Created new family!"
        roots = [self.treewidget]

        text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 'Enter family name:')
        if ok:
            for root in roots:
                family = Family( text )
                node = CustomTreeNode( root, family )
                node.setExpanded(True)
                self.treewidget.itemSelectionChanged.emit()


    def add_person_tree_node_clicked(self):
        print "Created new person!"
        roots = self.treewidget.selectedItems()

        text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 'Enter your name:')
        if ok:
            for root in roots:
                person = Person( text )
                node = CustomTreeNode( root, person )
                node.setExpanded(True)
                self.treewidget.itemSelectionChanged.emit()


    def center_window(self, window , cursor=False):
        qr = window.frameGeometry()
        cp = QtGui.QCursor.pos() if cursor else QtGui.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        window.move(qr.topLeft())


# __name__
# ------------------------------------------------------------------------------
if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    ex = ExampleWidget()
    sys.exit(app.exec_())