Skip to main content
 首页 » 编程设计

python之PyQt4 中的多次拖放

2025年05月04日19三少

我找不到使用 Qt/PyQt 拖放(和放下)多个元素的示例; 在我的例子中,我需要从这个 QTableView 中拖动元素:

class DragTable(QTableView): 
    def __init__(self, parent = None): 
        super(DragTable, self).__init__(parent) 
        self.setDragEnabled(True) 
 
    def dragEnterEvent(self, event): 
        if event.mimeData().hasFormat("application/pubmedrecord"): 
            event.setDropAction(Qt.MoveAction) 
            event.accept() 
        else: 
            event.ignore() 
 
    def startDrag(self, event): 
        print type(event) 
        index = self.indexAt(event.pos()) 
        if not index.isValid(): 
            return 
 
        selected = index.row() 
        bstream = cPickle.dumps(selected) 
        mimeData = QMimeData() 
        mimeData.setData("application/pubmedrecord", bstream) 
        drag = QDrag(self) 
        drag.setMimeData(mimeData) 
        pixmap = QPixmap(":/drag.png") 
 
        drag.setHotSpot(QPoint(pixmap.width()/3, pixmap.height()/3)) 
        drag.setPixmap(pixmap) 
        result = drag.start(Qt.MoveAction) 
 
    def mouseMoveEvent(self, event): 
        self.startDrag(event) 

到这个 QLabel(我的拖放区):

class TagLabel(QLabel): 
    def __init__(self, text, color, parent = None): 
        super(TagLabel, self).__init__(parent) 
        self.tagColor = color 
        self.setText(text) 
        self.setStyleSheet("QLabel { background-color: %s; font-size: 14pt; }" % self.tagColor) 
        self.defaultStyle = self.styleSheet() 
        self.setAlignment(Qt.AlignHCenter|Qt.AlignVCenter) 
        self.setAcceptDrops(True) 
 
    def dragEnterEvent(self, event): 
        if event.mimeData().hasFormat("application/pubmedrecord"): 
            self.set_bg(True) 
            event.accept() 
        else: 
            event.reject() 
 
    def dragLeaveEvent(self, event): 
        self.set_bg(False) 
        event.accept() 
 
    def dropEvent(self, event): 
        self.set_bg(False) 
        data = event.mimeData() 
        bstream = data.retrieveData("application/pubmedrecord", QVariant.ByteArray) 
        selected = pickle.loads(bstream.toByteArray()) 
        event.accept() 
        self.emit(SIGNAL("dropAccepted(PyQt_PyObject)"), (selected, str(self.text()), str(self.tagColor))) 
 
    def set_bg(self, active = False): 
        if active: 
            style = "QLabel {background: yellow; font-size: 14pt;}" 
            self.setStyleSheet(style) 
        else: 
            self.setStyleSheet(self.defaultStyle) 

有什么建议吗?谢谢!

请您参考如下方法:

这是一个完整的工作示例:

from PyQt4 import QtCore, QtGui, Qt 
import cPickle 
import pickle 

为什么要使用 cPickle 以及 pickle?

class DragTable(QtGui.QTableView): 
    def __init__(self, parent = None): 
        super(DragTable, self).__init__(parent) 
        self.setDragEnabled(True) 
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) 

您可能想在此处设置选择行为,因为我假设基于行的数据表示。您当然可以更改它。

    def dragEnterEvent(self, event): 
        if event.mimeData().hasFormat("application/pubmedrecord"): 
            event.setDropAction(Qt.MoveAction) 
            event.accept() 
        else: 
            event.ignore() 
 
    def startDrag(self, event): 

根据事件位置,您的代码在此假定只有一个索引。对于 QTableView,这是不必要的,因为它本身已经处理了鼠标点击。相反,最好依靠 Qt 一如既往地为您提供您实际需要的信息。在这里,我选择使用 selectedIndexes()。

        indices = self.selectedIndexes() 

Indices 现在是 QModelIndex 实例的列表,我选择将其转换为一组行号。也可以将它们转换为 QPersistentModelIndexes 列表,具体取决于您的需要。

这里可能会让您感到惊讶的是,无论选择行为如何,索引都包含表中所有单元格 的索引,而不是所有行。这就是我选择使用 set 而不是 list 的原因。

        selected = set() 
        for index in indices: 
            selected.add(index.row()) 

我没有动过其余部分,假设您知道自己在那里做什么。

        bstream = cPickle.dumps(selected) 
        mimeData = QtCore.QMimeData() 
        mimeData.setData("application/pubmedrecord", bstream) 
        drag = QtGui.QDrag(self) 
        drag.setMimeData(mimeData) 
        pixmap = QtGui.QPixmap(":/drag.png") 
 
        drag.setHotSpot(QtCore.QPoint(pixmap.width()/3, pixmap.height()/3)) 
        drag.setPixmap(pixmap) 
        result = drag.start(QtCore.Qt.MoveAction) 
 
    def mouseMoveEvent(self, event): 
        self.startDrag(event) 
 
 
class TagLabel(QtGui.QLabel): 
    def __init__(self, text, color, parent = None): 
        super(TagLabel, self).__init__(parent) 
        self.tagColor = color 
        self.setText(text) 
        self.setStyleSheet("QLabel { background-color: %s; font-size: 14pt; }" % self.tagColor) 
        self.defaultStyle = self.styleSheet() 
        self.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter) 
        self.setAcceptDrops(True) 
 
    def dragEnterEvent(self, event): 
        if event.mimeData().hasFormat("application/pubmedrecord"): 
            self.set_bg(True) 
            event.accept() 
        else: 
            event.reject() 
 
    def dragLeaveEvent(self, event): 
        self.set_bg(False) 
        event.accept() 
 
    def dropEvent(self, event): 
        self.set_bg(False) 
        data = event.mimeData() 
        bstream = data.retrieveData("application/pubmedrecord", QtCore.QVariant.ByteArray) 
        selected = pickle.loads(bstream.toByteArray()) 
        event.accept() 
        self.emit(QtCore.SIGNAL("dropAccepted(PyQt_PyObject)"), (selected, str(self.text()), str(self.tagColor))) 

除非您使用此信号与 C++ 代码交互,否则无需在此处添加信号参数,您也可以使用不带括号的 dropAccepted,PyQt4 会做正确的事情。

    def set_bg(self, active = False): 
        if active: 
            style = "QLabel {background: yellow; font-size: 14pt;}" 
            self.setStyleSheet(style) 
        else: 
            self.setStyleSheet(self.defaultStyle) 
 
 
 
app = QtGui.QApplication([]) 
 
l = TagLabel("bla bla bla bla bla bla bla", "red") 
l.show() 
 
m = QtGui.QStandardItemModel() 
for _ in xrange(4): 
    m.appendRow([QtGui.QStandardItem(x) for x in ["aap", "noot", "mies"]]) 
 
t = DragTable() 
t.setModel(m) 
t.show() 
 
def h(o): 
    print "signal handled", o 
l.connect(l, QtCore.SIGNAL("dropAccepted(PyQt_PyObject)"), h) 
 
app.exec_()