PyQt5 QListWidget拖拽优化:仅控件区域启用文件拖拽
我想给PyQt5的QListWidget实现拖拽功能,一开始在主窗口设置self.setAcceptDrops(True)时功能能生效,但拖拽范围是整个应用窗口,在窗口任意区域拖入文件都会加到QListWidget里。我的目标是只在QListWidget区域启用拖拽,折腾了半天没搞定,求解决办法。
初始代码
class okno_glowne(qtw.QWidget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ui = Ui_main_new() self.ui.setupUi(self) #self.setAcceptDrops(True) def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() print("Coś się udaje") else: event.ignore() def dropEvent(self, event): if event.mimeData().hasFormat('application/x-qabstractitemmodeldatalist'): if event.source() == self.ui.lista_excele_drag: event.acceptProposedAction() for url in event.mimeData().urls(): file_path = url.toLocalFile() self.ui.lista_excele_drag.addItem(file_path) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.drag_start_position = event.pos() def mouseMoveEvent(self, event): if not (event.buttons() & Qt.LeftButton): return if (event.pos() - self.drag_start_position).manhattanLength() <QApplication.startDragDistance(): return drag = QDrag(self) mime_data = QMimeData() mime_data.setText(self.ui.lista_excele_drag.currentItem().text()) drag.setMimeData(mime_data) drag.exec_(Qt.CopyAction) if __name__ == '__main__': app =qtw.QApplication([]) Widget = okno_glowne() Widget = QtWidgets.QStackedWidget() w = okno_glowne() w.show() app.exec_()
自定义QListWidget类的尝试
后来我把代码改成在单独文件mylistwidget.py里定义自定义类,知道可以通过提升部件来用,但功能有问题:dragEnterEvent能正常触发(有打印输出),但dropEvent和鼠标相关事件完全没反应。
修改后的代码:
from PyQt5.QtWidgets import * from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt, QMimeData from PyQt5.QtGui import QDrag class MyListWidget(QListWidget): # def __init__(self, *args, **kwargs): # QListWidget().__init__(self, *args, **kwargs) def __init__(self, parent=None): QListWidget().__init__(parent) self.setAcceptDrops(True) def dragEnterEvent(self, event): print("dragEnterEvent") if event.mimeData().hasUrls(): event.accept() else: event.ignore() def dropEvent(self, event): print("dropEvent") if event.mimeData().hasUrls(): for url in event.mimeData().urls(): path = url.toLocalFile() item = QListWidgetItem(path) self.addItem(item) event.accept() else: event.ignore() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.drag_start_position = event.pos() def mouseMoveEvent(self, event): if not (event.buttons() & Qt.LeftButton): return if (event.pos() - self.drag_start_position).manhattanLength() < QApplication.startDragDistance(): return drag = QDrag(self) mime_data = QMimeData() mime_data.setText(self.currentItem().text()) drag.setMimeData(mime_data) drag.exec_(Qt.CopyAction)
问题修复方案
自定义类里有两个关键错误导致功能异常:
初始化方法调用错误:
原代码中QListWidget().__init__(parent)是创建了一个新的QListWidget实例并调用其初始化方法,而非初始化当前的MyListWidget对象。正确写法应为super().__init__(parent)或QListWidget.__init__(self, parent)。拖拽事件处理逻辑不规范:
在dragEnterEvent中,应调用event.acceptProposedAction()而非event.accept(),这样才能正确响应拖拽动作;同时鼠标事件中需先调用父类方法,确保选中项的正常逻辑。
另外要确保在UI设计器中正确提升部件:将QListWidget提升为自定义的MyListWidget,并指定对应的模块文件(mylistwidget)。
修正后的MyListWidget代码
from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QApplication from PyQt5.QtCore import Qt, QMimeData from PyQt5.QtGui import QDrag class MyListWidget(QListWidget): def __init__(self, parent=None): super().__init__(parent) # 正确初始化父类 self.setAcceptDrops(True) # 启用内部拖拽排序(按需开启) self.setDragEnabled(True) self.setDropIndicatorShown(True) self.setDefaultDropAction(Qt.CopyAction) def dragEnterEvent(self, event): print("dragEnterEvent triggered") # 区分外部文件拖拽与内部项拖拽 if event.mimeData().hasUrls(): event.acceptProposedAction() elif event.mimeData().hasFormat('application/x-qabstractitemmodeldatalist'): super().dragEnterEvent(event) else: event.ignore() def dropEvent(self, event): print("dropEvent triggered") if event.mimeData().hasUrls(): # 处理外部文件拖拽 for url in event.mimeData().urls(): file_path = url.toLocalFile() self.addItem(QListWidgetItem(file_path)) event.acceptProposedAction() else: # 处理内部项拖拽,交给父类逻辑 super().dropEvent(event) def mousePressEvent(self, event): # 先调用父类方法确保选中项正常工作 super().mousePressEvent(event) if event.button() == Qt.LeftButton: self.drag_start_position = event.pos() def mouseMoveEvent(self, event): if not (event.buttons() & Qt.LeftButton): return # 检查拖拽距离是否达到系统阈值 if (event.pos() - self.drag_start_position).manhattanLength() < QApplication.startDragDistance(): return # 获取当前选中项,避免空指针 current_item = self.currentItem() if not current_item: return # 创建拖拽对象并设置数据 drag = QDrag(self) mime_data = QMimeData() mime_data.setText(current_item.text()) # 添加内部拖拽支持的格式 mime_data.setData('application/x-qabstractitemmodeldatalist', b'') drag.setMimeData(mime_data) drag.exec_(Qt.CopyAction)
主窗口代码调整
主窗口无需再实现拖拽相关事件,删除原有的dragEnterEvent、dropEvent、mousePressEvent、mouseMoveEvent方法,且不要设置self.setAcceptDrops(True),确保拖拽仅在MyListWidget区域生效。
import qtw from mylistwidget import MyListWidget # 导入自定义类 class okno_glowne(qtw.QWidget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ui = Ui_main_new() self.ui.setupUi(self) if __name__ == '__main__': app = qtw.QApplication([]) w = okno_glowne() w.show() app.exec_()
内容的提问来源于stack exchange,提问作者Sebastian Kasperski

