You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt6:如何避免拖放事件中列表项重叠并下移已有项

PyQt6列表项拖拽重叠问题解决

问题说明

使用PyQt6开发跨组件拖拽列表项的GUI时,拖放目标位置的列表项会重叠。期望效果是拖放时目标位置及下方的已有列表项自动下移,为新项腾出空间。尝试过统计目标组件现有项数量、对比插入索引来手动下移,但插入行索引仅在拖放完成后才生成,无法提前处理。

当前代码

import sys
from PyQt6.QtWidgets import QApplication, QWidget, QListView, QAbstractItemView, QHBoxLayout, QVBoxLayout, QLabel
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QStandardItemModel, QIcon, QStandardItem

class Draggable_ListView(QListView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)
        self.setIconSize(QSize(150, 150))
        self.setDragDropMode(QAbstractItemView.DragDropMode.DragDrop)
        self.setResizeMode(QListView.ResizeMode.Adjust)
        self.setViewMode(QListView.ViewMode.ListMode)

    
    def dropEvent(self, event):     
        super().dropEvent(event) 
        event.setDropAction(Qt.DropAction.MoveAction)

class ListView_Left(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        self.subtitle = 'List 1'

        layout = QVBoxLayout()
        self.setLayout(layout)

        subtitle_label = QLabel(self.subtitle)
        layout.addWidget(subtitle_label, alignment=Qt.AlignmentFlag.AlignCenter)

        self.list_view = Draggable_ListView(self)
        layout.addWidget(self.list_view)

    def set_model(self, model):
        self.list_view.setModel(model)

    def get_model(self):
        return self.list_view.model()

class ListView_Right(QWidget):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.subtitle = 'List 2'

        layout = QVBoxLayout()
        self.setLayout(layout)

        subtitle_label = QLabel(self.subtitle)
        layout.addWidget(subtitle_label, alignment=Qt.AlignmentFlag.AlignCenter)

        self.list_view = Draggable_ListView(self)
        layout.addWidget(self.list_view)

    def set_model(self, model):
        self.list_view.setModel(model)

    def get_model(self):
        return self.list_view.model()

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.window_width, self.window_height = 1400, 500
        self.setMinimumSize(self.window_width, self.window_height)
        self.resize(self.window_width, self.window_height)

        layout = QHBoxLayout()
        self.setLayout(layout)

        self.listViewLeft = ListView_Left(self)
        layout.addWidget(self.listViewLeft)

        self.listViewRight = ListView_Right(self)
        layout.addWidget(self.listViewRight)

        icon = QStandardItem()
        icon.setIcon(QIcon(r"path to JPG image"))
        icon_2 = QStandardItem()
        icon_2.setIcon(QIcon(r"path to JPG image_1"))
        icon_3 = QStandardItem()
        icon_3.setIcon(QIcon(r"path to JPG image_2"))

        
        self.listViewLeft.set_model(QStandardItemModel(self))
        self.listViewLeft.get_model().appendRow(icon)
        self.listViewLeft.get_model().appendRow(icon_2)
        self.listViewLeft.get_model().appendRow(icon_3)

        self.listViewRight.set_model(QStandardItemModel(self))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    
    myApp = MyApp()
    myApp.show()

    sys.exit(app.exec())

解决方案

核心问题是默认拖放逻辑未正确触发目标位置的项位移,通过重写拖拽相关事件、配置视图属性,结合手动处理跨模型拖拽数据迁移,可实现预期效果:

修改后的Draggable_ListView类

class Draggable_ListView(QListView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)
        self.setIconSize(QSize(150, 150))
        # 配置拖拽模式为支持跨组件拖拽和内部移动
        self.setDragDropMode(QAbstractItemView.DragDropMode.DragDrop)
        self.setDefaultDropAction(Qt.DropAction.MoveAction)
        self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
        self.setResizeMode(QListView.ResizeMode.Adjust)
        self.setViewMode(QListView.ViewMode.ListMode)
        # 显示插入位置指示器,明确提示用户拖放位置
        self.setDropIndicatorShown(True)

    def dragMoveEvent(self, event):
        # 允许跨组件或内部的移动拖拽动作
        if event.source() != self or event.possibleActions() & Qt.DropAction.MoveAction:
            event.setDropAction(Qt.DropAction.MoveAction)
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        # 处理跨不同模型的拖拽
        if event.source() != self:
            mime_data = event.mimeData()
            if mime_data.hasFormat('application/x-qabstractitemmodeldatalist'):
                target_model = self.model()
                # 获取插入位置:拖到空白区域则插入到末尾,否则插入到目标索引上方
                index = self.indexAt(event.pos())
                insert_row = target_model.rowCount() if not index.isValid() else index.row()
                
                # 从源模型复制数据到目标模型,并删除源项
                source_model = event.source().model()
                for idx in event.source().selectedIndexes():
                    item = source_model.itemFromIndex(idx)
                    target_model.insertRow(insert_row, item.clone())
                    source_model.removeRow(idx.row())
                
                event.setDropAction(Qt.DropAction.MoveAction)
                event.accept()
        else:
            # 内部拖拽使用默认处理逻辑
            super().dropEvent(event)

关键说明

  • setDropIndicatorShown(True):开启插入位置指示器,拖拽时显示一条线提示插入位置,提升用户体验。
  • 跨模型拖拽手动处理数据:默认拖放逻辑在跨不同QStandardItemModel时无法自动完成插入下移,因此需要手动获取源项数据、插入到目标位置并删除源项。
  • dragMoveEvent中明确允许移动动作,确保拖拽过程中视图正确响应并显示插入提示。

内容的提问来源于stack exchange,提问作者Shubham

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 01:46:02