首页 > 解决方案 > PyQt5在MainWindow上使用拖放移动按钮

问题描述

我开始使用 PyQt5,我对下面示例中 mimedata 的使用有疑问。

该示例允许通过使用 MainWindow 上的拖放事件来移动 Button。

让我感到困惑的是,在 MyButton 类中,它设置了一个没有任何数据设置的 QmimeData,然后直接将其传递给 QDrag 对象。

而且我确实尝试删除 QMimedata 部分,这确实使拖放不再起作用,这似乎是必要的设置。

我的问题是,在这种情况下,这里的 Qmimedata 是什么?因为从 Qmimedata 的描述来看,现在应该没有数据了,那么它是如何工作的呢?

谢谢!

from PyQt5 import QtCore, QtGui, QtWidgets
import sys

class MyButton(QtWidgets.QPushButton):

    def __init__(self, parent=None):
        super().__init__(parent)

    def mouseMoveEvent(self, e):

        if e.buttons() != QtCore.Qt.RightButton:
            return

        mimeData = QtCore.QMimeData()
        drag = QtGui.QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(e.pos() - self.rect().topLeft())
        drag.exec_(QtCore.Qt.MoveAction)

    def mousePressEvent(self, e):
        super().mousePressEvent(e)

        if e.button() == QtCore.Qt.LeftButton:
            print('press')


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = MyButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(220, 180, 331, 151))
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
         self.pushButton.setText(_translate("MainWindow", "Move it"))



class Example(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
      super().__init__(parent)
      self.ui=Ui_MainWindow()
      self.ui.setupUi(self)

      self.setAcceptDrops(True)



    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        position = e.pos()
        self.ui.pushButton.move(position)

        e.setDropAction(QtCore.Qt.MoveAction)
        e.accept()





apps = QtWidgets.QApplication(sys.argv)
ex = Example()
ex.show()
apps.exec_()

标签: pythonpyqt5

解决方案


似乎您正在尝试使用从创建的代码pyuic,它应该被编辑或“模仿”:它是编译后的代码,应该保持原样,并从您写在不同文件中的实际代码中使用,正如这里所解释的。

总之,最重要的部分是了解 Qt 的拖放实现是如何工作的。我建议您仔细阅读官方文档:虽然它是为 C++ 开发人员编写的,但概念是相同的。它可能看起来势不可挡,而且不知何故过于复杂,但它有很多严肃而充分的理由让它如此运作。

基本上,当创建拖动对象时,它必须附加一些数据,否则没有应用程序(包括您自己的)会知道如何处理它。对于这个用例,您至少需要两件事:

  • 拖动对象包含什么样的“数据”,否则可以在您的应用程序中拖动任何类型的对象(例如,用户正在拖动图像并错误地释放了窗口上的鼠标按钮),如果您不这样做检查您是否会移动您的按钮,即使用户不想这样做。
  • 你要移动什么小部件(你的情况下只有一个小部件,但将来可能会改变)

这是通过将一些数据附加到以 MIME 格式设置的 QDrag 对象来完成的。由于这是一种特殊情况(请参阅Qt 提供的标准类型的QMimeData文档),您需要创建自己的 mimeData 格式并提供一些数据。为此,您需要一个 QByteArray 和一个设置为写入模式的 QDataStream 到数组,这将用于写入拖动对象所需的实际数据。

在创建并“执行”拖动对象后,您需要确保接收者 dragEnterEvent 的事件实际上包含您感兴趣的 MIME 格式,该格式实际上存在于事件的 mimeData 中,以便您可以读取其数据并实际执行有用的东西。

我创建了一个非常基本的示例,它允许您使用拖放来移动界面中的按钮,但请注意,这会产生严重影响:在这种情况下,只有一个父小部件(centralWidget),但如果您要使用一些更复杂的布局,可能通过使用框架或组框,它不会工作。将一个小部件添加到主小部件的子级将使后者成为前者的父级,这将使其位置相对于父级位置。
例如,假设您创建了一个包含两个组框的布局,并且您已将按钮添加到第二个。在这种情况下,除非您重新设置它,否则您将无法轻松地将其移动到第一个框,并且如果您尝试在第二个框内移动(它已经在哪里),您需要根据那个盒子,只要你在主窗口中捕捉到 dropEvent。更重要的是:如果您有一些复杂的布局(带有嵌套的小部件和布局),您需要找到一种方法来获取您要拖入的实际小部件。

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class MyButton(QtWidgets.QPushButton):
    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        if event.button() == QtCore.Qt.LeftButton:
            print('press')
        elif event.button() == QtCore.Qt.RightButton:
            # save the click position to keep it consistent when dragging
            self.mousePos = event.pos()

    def mouseMoveEvent(self, event):
        if event.buttons() != QtCore.Qt.RightButton:
            return
        mimeData = QtCore.QMimeData()
        # create a byte array and a stream that is used to write into
        byteArray = QtCore.QByteArray()
        stream = QtCore.QDataStream(byteArray, QtCore.QIODevice.WriteOnly)
        # set the objectName and click position to keep track of the widget
        # that we're moving and it's click position to ensure that it will
        # be moved accordingly
        stream.writeQString(self.objectName())
        stream.writeQVariant(self.mousePos)
        # create a custom mimeData format to save the drag info
        mimeData.setData('myApp/QtWidget', byteArray)
        drag = QtGui.QDrag(self)
        # add a pixmap of the widget to show what's actually moving
        drag.setPixmap(self.grab())
        drag.setMimeData(mimeData)
        # set the hotspot according to the mouse press position
        drag.setHotSpot(self.mousePos - self.rect().topLeft())
        drag.exec_()


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        super().__init__()
        self.resize(800, 600)
        centralWidget = QtWidgets.QWidget()
        self.setCentralWidget(centralWidget)
        self.pushButton = MyButton(centralWidget, objectName='pushButton')
        self.pushButton.setGeometry(QtCore.QRect(220, 180, 331, 151))
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        # only accept our mimeData format, ignoring any other data content
        if event.mimeData().hasFormat('myApp/QtWidget'):
            event.accept()

    def dropEvent(self, event):
        stream = QtCore.QDataStream(event.mimeData().data('myApp/QtWidget'))
        # QDataStream objects should be read in the same order as they were written
        objectName = stream.readQString()
        # find the child widget that has the objectName set within the drag event
        widget = self.findChild(QtWidgets.QWidget, objectName)
        if not widget:
            return
        # move the widget relative to the original mouse position, so that
        # it will be placed exactly where the user drags it and according to
        # the original click position
        widget.move(event.pos() - stream.readQVariant())


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

推荐阅读