it's been weeks that I am trying to implement Drag and Drop in my QTreeView. So i decided to create this post to find a solution. I searched on many websites, on the Qt's official website, and on github but each time it doesn't work. I also tried to ask chatGPT if it can show me an exemple of drag and drop in a QTreeView but I didn't work either. So here is what I am trying to achieve : I want to create a QTreeView from the QAbstractItemModel class. Then I would like to be able to reorder my items for exemple moving the last item to the second row, or move it on an other item in order to insert it as a children of this item. It would be nice if I also could move a child in order to make a top level item. By the way, I am using PySide6 so maybe the problem comes from the module ?
Here is a piece of code to work on, it already has a model with children so you can test every methods in it and add those you need to implement drag and drop :
import sys
from PySide6.QtGui import *
from PySide6.QtCore import *
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self, parent: QWidget = None):
super().__init__(parent)
self.resize(573, 468)
self.frame = QFrame()
self.setCentralWidget(self.frame)
self.hlayout = QHBoxLayout()
self.frame.setLayout(self.hlayout)
self.view = QTreeView()
self.view.setAlternatingRowColors(True)
self.view.setSelectionBehavior(QAbstractItemView.SelectItems)
self.view.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
self.view.setAnimated(False)
self.view.setAllColumnsShowFocus(True)
self.hlayout.addWidget(self.view)
headers = ["value",'Type','Progress Bar','Test','Test','Test','Test']
self.model = TreeModel(headers, self)
# self.proxyModel = QSortFilterProxyModel(self)
# self.proxyModel.setSourceModel(self.model)
self.view.setModel(self.model)
self.view.expandAll()
for column in range(self.model.columnCount()):
self.view.resizeColumnToContents(column)
class TreeItem:
def __init__(self, data: list, parent: 'TreeItem' = None):
self.item_data = data
self.parent_item = parent
self.child_items = []
def child(self, number: int) -> 'TreeItem':
if number < 0 or number >= len(self.child_items):
return None
return self.child_items[number]
def last_child(self):
return self.child_items[-1] if self.child_items else None
def child_count(self) -> int:
return len(self.child_items)
def child_number(self) -> int:
if self.parent_item:
return self.parent_item.child_items.index(self)
return 0
def column_count(self) -> int:
return len(self.item_data)
def data(self, column: int):
if column < 0 or column >= len(self.item_data):
return None
return self.item_data[column]
def insert_children(self, position: int, count: int, columns: int) -> bool:
if position < 0 or position > len(self.child_items):
return False
for row in range(count):
data = [None] * columns
item = TreeItem(data.copy(), self)
self.child_items.insert(position, item)
return True
def remove_children(self, position: int, count: int) -> bool:
if position < 0 or position + count > len(self.child_items):
return False
for row in range(count):
self.child_items.pop(position)
return True
def parent(self):
return self.parent_item
def set_data(self, column: int, value):
if column < 0 or column >= len(self.item_data):
return False
self.item_data[column] = value
return True
def __repr__(self) -> str:
result = f"<treeitem.TreeItem at 0x{id(self):x}"
for d in self.item_data:
result += f' "{d}"' if d else " <None>"
result += f", {len(self.child_items)} children>"
return result
class TreeModel(QAbstractItemModel):
def __init__(self, headers: list, parent=None):
super().__init__(parent)
self.root_data = headers
self.root_item = TreeItem(self.root_data.copy())
self.setup_model_data(self.root_item)
def columnCount(self, parent: QModelIndex = None) -> int:
return self.root_item.column_count()
def data(self, index: QModelIndex, role: int = None):
if not index.isValid():
return None
if role != Qt.DisplayRole and role != Qt.EditRole:
return None
item: TreeItem = self.get_item(index)
return item.data(index.column())
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
if not index.isValid():
return Qt.NoItemFlags
return Qt.ItemIsEditable | QAbstractItemModel.flags(self, index)
def get_item(self, index: QModelIndex = QModelIndex()) -> TreeItem:
if index.isValid():
item: TreeItem = index.internalPointer()
if item:
return item
return self.root_item
def headerData(self, section: int, orientation: Qt.Orientation,
role: int = Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.root_item.data(section)
return None
def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
if parent.isValid() and parent.column() != 0:
return QModelIndex()
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return QModelIndex()
child_item: TreeItem = parent_item.child(row)
if child_item:
return self.createIndex(row, column, child_item)
return QModelIndex()
def insertColumns(self, position: int, columns: int,
parent: QModelIndex = QModelIndex()) -> bool:
self.beginInsertColumns(parent, position, position + columns - 1)
success: bool = self.root_item.insert_columns(position, columns)
self.endInsertColumns()
return success
def insertRows(self, position: int, rows: int,
parent: QModelIndex = QModelIndex()) -> bool:
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return False
self.beginInsertRows(parent, position, position + rows - 1)
column_count = self.root_item.column_count()
success: bool = parent_item.insert_children(position, rows, column_count)
self.endInsertRows()
return success
def parent(self, index: QModelIndex = QModelIndex()) -> QModelIndex:
if not index.isValid():
return QModelIndex()
child_item: TreeItem = self.get_item(index)
if child_item:
parent_item: TreeItem = child_item.parent()
else:
parent_item = None
if parent_item == self.root_item or not parent_item:
return QModelIndex()
return self.createIndex(parent_item.child_number(), 0, parent_item)
def removeColumns(self, position: int, columns: int,
parent: QModelIndex = QModelIndex()) -> bool:
self.beginRemoveColumns(parent, position, position + columns - 1)
success: bool = self.root_item.remove_columns(position, columns)
self.endRemoveColumns()
if self.root_item.column_count() == 0:
self.removeRows(0, self.rowCount())
return success
def removeRows(self, position: int, rows: int,
parent: QModelIndex = QModelIndex()) -> bool:
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return False
self.beginRemoveRows(parent, position, position + rows - 1)
success: bool = parent_item.remove_children(position, rows)
self.endRemoveRows()
return success
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
if parent.isValid() and parent.column() > 0:
return 0
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return 0
return parent_item.child_count()
def setData(self, index: QModelIndex, value, role: int) -> bool:
if role != Qt.EditRole:
return False
item: TreeItem = self.get_item(index)
result: bool = item.set_data(index.column(), value)
if result:
self.dataChanged.emit(index, index, [Qt.DisplayRole, Qt.EditRole])
return result
def setHeaderData(self, section: int, orientation: Qt.Orientation, value,
role: int = None) -> bool:
if role != Qt.EditRole or orientation != Qt.Horizontal:
return False
result: bool = self.root_item.set_data(section, value)
if result:
self.headerDataChanged.emit(orientation, section, section)
return result
def setup_model_data(self, parent: TreeItem):
parents = [parent]
Data = [["Test 1",'diam',10,2,3,4,5,6],["Test 2",'len',55,2,3,4,5,6],["Test 3",'conc',64,2,3,4,5,6],["Test 4",'diam',9,2,3,4,5,6],["Test 5",'len',12,2,3,4,5,6]]
for item in Data :
parent : TreeItem = parents[0]
col_count = self.root_item.column_count()
parent.insert_children(parent.child_count(), 1, col_count)
column_data = [item[0],item[1],item[2],item[3],item[4],item[5],item[6]]
for column in range(len(column_data)):
child = parent.last_child()
child.set_data(column, column_data[column])
parent2 : TreeItem = parent.last_child()
parent2.insert_children(parent2.child_count(), 1, col_count)
column_data = [item[0],item[1],item[2],item[3],item[4],item[5],item[6]]
for column in range(len(column_data)):
child = parent2.last_child()
child.set_data(column, column_data[column])
if __name__=="__main__" :
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Every try that I gave crashed with each time different error messages. Most of the time, I can only drop items on other but I can never insert them in the middle of others. So I think that starting back from scratch is the best solution to understand how it works and how to implement it correctly. I really hope that someone will find an answer and I thank you for taking the time to read this post.