[PyQt] Drag & Drop with QTreeWidget

Baz Walter bazwal at ftml.net
Mon Dec 14 23:27:18 GMT 2009


NARCISO, Rui wrote:
> It worked nicely up to the sorting part.
> 
> And if th euser sorts the data himself, then eveything is ok.
> 
> It's just when I try to implement default sorting that that fails.
> 
> Maybe I can pick up on a signal telling that sorting is in progress to override this behaviour in the rowsInserted function...
> 
> -----Message d'origine-----
> De : Baz Walter [mailto:bazwal at ftml.net]
> Envoyé : vendredi 11 décembre 2009 21:46
> À : NARCISO, Rui
> Cc : PyQt (E-mail)
> Objet : Re: [PyQt] Drag & Drop with QTreeWidget
> 
> 
> NARCISO, Rui wrote:
>> Hi again
>>
>> Your approach worked nicely but when I tried to enable sorting using:
>>
>>          self.setSortingEnabled(True)
>>          self.sortByColumn(0, QtCore.Qt.AscendingOrder)
>> I get a segmentation fault.
>>
>> If i disable the sortByColumn then it works.
>>
>> How then to set the sorting using column 0 by default ?
> 
> actually, i now think my approach was too crude. the trouble with using 
> rowsInserted is that it is called every time items are added or moved 
> (or sorted).
> 
> what is really needed is a way to add special handling only for dropped 
> items. unfortunately, there does not appear to be an obvious way to 
> detect what items were dropped and where. so i think the only reliable 
> way to get the behaviour you want is to reimplement qtreewidget's drop 
> event handling (which is probably doable, but not easy).

i had another look at reimplementing qtreewidget's drop event handling 
and it is in fact much easier than i thought. the code below simply 
reproduces the current behaviour (it's mostly a port of the qt source).
hopefully it should not be too difficult to see how to adapt the code in 
the moveSelection function to get whatever alternate behaviours you 
want. (note that this example assumes internal moves only).


import sys
from PyQt4 import QtGui, QtCore


class TreeWidget(QtGui.QTreeWidget):
     def __init__(self, parent=None):
         QtGui.QTreeWidget.__init__(self, parent)
         self.header().setHidden(True)
         self.setSelectionMode(self.ExtendedSelection)
         self.setDragDropMode(self.InternalMove)
         self.setDragEnabled(True)
         self.setDropIndicatorShown(True)
         def add(num):
             item = QtGui.QTreeWidgetItem(
                     self, QtCore.QStringList('Item(%i)' % num))
             for i in xrange(1, 4):
                 QtGui.QTreeWidgetItem(
                     item, QtCore.QStringList('Child(%i,%i)' % (num, i)))
             item.setExpanded(True)
         for i in xrange(1, 3):
             add(i)

     def dropEvent(self, event):
         if event.source() == self:
             QtGui.QAbstractItemView.dropEvent(self, event)

     def dropMimeData(self, parent, row, data, action):
         if action == QtCore.Qt.MoveAction:
             return self.moveSelection(parent, row)
         return False

     def moveSelection(self, parent, position):
	# save the selected items
         selection = [QtCore.QPersistentModelIndex(i)
                      for i in self.selectedIndexes()]
         parent_index = self.indexFromItem(parent)
         if parent_index in selection:
             return False
         # save the drop location in case it gets moved
         target = self.model().index(position, 0, parent_index).row()
         if target < 0:
             target = position
         # remove the selected items
         taken = []
         for index in reversed(selection):
             item = self.itemFromIndex(QtCore.QModelIndex(index))
             if item is None or item.parent() is None:
                 taken.append(self.takeTopLevelItem(index.row()))
             else:
                 taken.append(item.parent().takeChild(index.row()))
         # insert the selected items at their new positions
         while taken:
             if position == -1:
                 # append the items if position not specified
                 if parent_index.isValid():
                     parent.insertChild(
                         parent.childCount(), taken.pop(0))
                 else:
                     self.insertTopLevelItem(
                         self.topLevelItemCount(), taken.pop(0))
             else:
		# insert the items at the specified position
                 if parent_index.isValid():
                     parent.insertChild(min(target,
                         parent.childCount()), taken.pop(0))
                 else:
                     self.insertTopLevelItem(min(target,
                         self.topLevelItemCount()), taken.pop(0))
         return True


if __name__ == "__main__":
     app = QtGui.QApplication(sys.argv)
     tree = TreeWidget()
     tree.resize(200, 300)
     tree.move(300, 300)
     tree.show()
     sys.exit(app.exec_())




More information about the PyQt mailing list