I'm trying to implement a simple drag and drop functionality from one QListView to another QListView by using PySide6.
Here ist the ui:
import sys
import qdarktheme
from PySide6.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QListView
from models.pipeline_model import PipelineListModel
from models.toolbox_model import ToolboxListModel, MyModel
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
toolbox_list_model = ToolboxListModel()
pipeline_list_model = PipelineListModel()
for i in range(10):
toolbox_list_model.toolbox_items.append(MyModel(x=i))
toolbox_list_view = QListView()
toolbox_list_view.setDragEnabled(True)
toolbox_list_view.setModel(toolbox_list_model)
pipeline_list_view = QListView()
pipeline_list_view.setAcceptDrops(True)
pipeline_list_view.setModel(pipeline_list_model)
vbox = QVBoxLayout()
vbox.addWidget(toolbox_list_view)
vbox.addWidget(pipeline_list_view)
main_widget = QWidget()
main_widget.setLayout(vbox)
self.setCentralWidget(main_widget)
if __name__ == '__main__':
app = QApplication()
app.setStyleSheet(qdarktheme.load_stylesheet())
main_window = MainWindow()
main_window.show()
# Start the event loop.
sys.exit(app.exec())
The Toolbox listmodel (where the items should be dragged from):
from typing import Union, Any
import PySide6
from PySide6 import QtCore
from PySide6.QtCore import Qt
from models.model_data import MyModel
class ToolboxListModel(QtCore.QAbstractListModel):
def __init__(self):
super().__init__()
self.toolbox_items: list[MyModel] = []
def data(self, index: Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex],
role: int = ...) -> Any:
if role == Qt.DisplayRole:
model = self.toolbox_items[index.row()]
return model.x
def rowCount(self, parent: Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex] = ...) -> int:
return len(self.toolbox_items)
The target listmodel (where the toolbox items shall be dropped):
from typing import Union, Any
import PySide6
from PySide6 import QtCore
from PySide6.QtCore import Qt
from models.model_data import MyModel
class PipelineListModel(QtCore.QAbstractListModel):
def __init__(self):
super().__init__()
self.toolbox_items: list[MyModel] = []
def data(self, index: Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex],
role: int = ...) -> Any:
if role == Qt.DisplayRole:
model = self.toolbox_items[index.row()]
return model.x
def rowCount(self, parent: Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex] = ...) -> int:
return len(self.toolbox_items)
and finally the shared datamodel:
import dataclasses
@dataclasses.dataclass
class MyModel:
x: int
Even though, the dragEnabled is set to True and the receiver QListView is set to accept the drop, the drag operation doesn't even start.
What am I doing wrong here? How to do the d'n'd operation correctly?
CodePudding user response:
As explained in the model view documentation (which I strongly suggest to read in its entirety), you must override the flags()
function, because the default behavior only makes items enabled and selectable.
Setting the Drag
flag on the view only allows drag operations, but only the model can tell what items can be dragged.
If you want to allow an index to be draggable, you must also provide the ItemIsDragEnabled
flag:
class ToolboxListModel(QtCore.QAbstractListModel):
def flags(self, index):
flags = super().flags(index)
if index.isValid():
flags |= QtCore.Qt.ItemFlags.ItemIsDragEnabled
return flags
Then, depending on the behavior you want on the target model, you may need to override flags to accept drops onto existing items (to overwrite items), or implement other functions as shown in the documentation about subclassing QAbstractListModel, since it doesn't support resizable lists by default.