样例仍然来自《Rapid GUI Programming with Python and Qt》一书。
中文版240页对拖放的支持有一段很重要的说明,但是有点语焉不详,所以结合代码部分重新给自己解释一下。
书中说:
setAcceptDrops()方法从QWidget继承而来,不过setDragEnabled()则不是,因此在默认情况下,只有部分窗口部件中它才是可用的。如果打算创建一个可以支持放下操作的自定义窗口部件,只需要简单调用setAcceptDrops(True)并重新实现dragEnterEvent()、dragMoveEvent、dropEvent()即可,正如在前面的例子所做的那样。如果也希望这个自定义窗口能够支持拖动,而该窗口是从QWidget继承而来,或者是从一些没有setDragEnabled()的QWidget子类继承而来,就必须做两件事来让该部件支持drag。一件事是提供一个startDrag()方法,以便可以创建一个QDrag对象,另一件事是确保在适当的时间能够调用startDrag()方法。要确保startDrag()的调用最简单的方法就是对mouseMoveEvent的重新实现……
回过头来重新看一遍代码,是怎样落实上面的说法的。
class DropLineEdit(QLineEdit):
"""QLineEdit继承自QWidget,要实现它需要四个step来支持'放下'"""
def __init__(self, parent=None):
super(DropLineEdit, self).__init__(parent)
self.setAcceptDrops(True) #step1 True
self.setToolTip("DropLineEdit")
def dragEnterEvent(self, event): #step2 dragEnterEvent:accept
if event.mimeData().hasFormat("application/x-icon-and-text"):
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event): #step3 dragMoveEvent:Copy or Move
if event.mimeData().hasFormat("application/x-icon-and-text"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event): #step4 dropEvent:get data
if event.mimeData().hasFormat("application/x-icon-and-text"):
data = event.mimeData().data("application/x-icon-and-text")
stream = QDataStream(data, QIODevice.ReadOnly)
text = stream.readQString()
self.setText(text)
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
第二个例子,祖上有德,支持drag的
class DnDListWidget(QListWidget):
"""QListWidget多重继承,其中一个父类是QAbstractItemView,支持setDragEnabled(),
所以可以直接设置为True,但是还要要有startDrag()方法以创建Drag对象"""
def __init__(self, parent=None):
super(DnDListWidget, self).__init__(parent)
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setToolTip("DnDListWidget")
def dragEnterEvent(self, event):
if event.mimeData().hasFormat("application/x-icon-and-text"):
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasFormat("application/x-icon-and-text"):
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasFormat("application/x-icon-and-text"):
data = event.mimeData().data("application/x-icon-and-text")
stream = QDataStream(data, QIODevice.ReadOnly)
text = stream.readQString()
# text=""
# stream>>text
icon = QIcon()
stream >> icon
item = QListWidgetItem(text, self)
item.setIcon(icon)
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.ignore()
def startDrag(self, dropActions):
item = self.currentItem()
icon = item.icon()
data = QByteArray()
stream = QDataStream(data, QIODevice.WriteOnly)
stream.writeQString(item.text())
# stream<<item.text()
stream << icon
mimeData = QMimeData()
mimeData.setData("application/x-icon-and-text", data)
drag = QDrag(self)
drag.setMimeData(mimeData)
pixmap = icon.pixmap(24, 24)
drag.setHotSpot(QPoint(12, 12))
drag.setPixmap(pixmap)
if drag.exec(Qt.MoveAction) == Qt.MoveAction:
self.takeItem(self.row(item))
第三个例子就是自我奋斗型的凤凰男
class DnDWidget(QWidget):
"""本类直接继承自Qwidget,所以天生不支持Drag,必须自行实现startDrag()和和适当时机的调用,本例是在mouseMoveEvent"""
def __init__(self, text, icon=QIcon(), parent=None):
super(DnDWidget, self).__init__(parent)
self.setAcceptDrops(True) #drop step1
#self.setDragEnabled(True),如果硬加上,也出错'DnDWidget' object has no attribute 'setDragEnabled'
self.text = text
self.icon = icon
self.setToolTip("DnDWidget")
def minimumSizeHint(self):
fm = QFontMetricsF(self.font())
if self.icon.isNull():
return QSize(fm.width(self.text), fm.height() * 1.5)
return QSize(34 + fm.width(self.text), max(34, fm.height() * 1.5))
def paintEvent(self, event):
height = QFontMetricsF(self.font()).height()
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setRenderHint(QPainter.TextAntialiasing)
painter.fillRect(self.rect(), QColor(Qt.yellow).lighter())
if self.icon.isNull():
painter.drawText(10, height, self.text)
else:
pixmap = self.icon.pixmap(24, 24)
painter.drawPixmap(0, 5, pixmap)
painter.drawText(34, height, self.text + " (Drag to or from me!)")
def dragEnterEvent(self, event): #drops step2
if event.mimeData().hasFormat("application/x-icon-and-text"):
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):#drop step3
if event.mimeData().hasFormat("application/x-icon-and-text"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event): #drop step4 over
if event.mimeData().hasFormat("application/x-icon-and-text"):
data = event.mimeData().data("application/x-icon-and-text")
stream = QDataStream(data, QIODevice.ReadOnly)
self.text=""
self.text = stream.readQString()
self.icon = QIcon()
stream >> self.icon
event.setDropAction(Qt.CopyAction)
event.accept()
self.updateGeometry()
self.update()
else:
event.ignore()
def mouseMoveEvent(self, event):
self.startDrag() #drag step 1
QWidget.mouseMoveEvent(self, event)
def startDrag(self): #drag step 2
icon = self.icon
if icon.isNull():
return
data = QByteArray()
stream = QDataStream(data, QIODevice.WriteOnly)
stream.writeQString(self.text)
# stream<<self.text
stream << icon
mimeData = QMimeData()
mimeData.setData("application/x-icon-and-text", data)
drag = QDrag(self)
drag.setMimeData(mimeData)
pixmap = icon.pixmap(24, 24)
drag.setHotSpot(QPoint(12, 12))
drag.setPixmap(pixmap)
drag.exec(Qt.CopyAction)