【问题标题】:PyQt5 Move Button on MainWindow with Drag/DropPyQt5在MainWindow上使用拖放移动按钮
【发布时间】:2019-07-27 12:50:11
【问题描述】:

我开始使用 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_()

【问题讨论】:

    标签: python pyqt5


    【解决方案1】:

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

    无论如何,最重要的部分是了解 Qt 的拖放实现是如何工作的。我建议您仔细阅读official documentation:虽然它是为 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_())
    

    【讨论】:

    • 非常感谢您的详细回复。是的,我提供的示例中的 Qmimiedata 让我很困惑,因为没有解决数据。在阅读了您的解释后,我可以更完整地了解如何使用它,我需要创建一个自定义 mimedata 类型。再次感谢!
    猜你喜欢
    • 2021-05-17
    • 1970-01-01
    • 1970-01-01
    • 2017-05-27
    • 2018-10-18
    • 2020-08-06
    • 1970-01-01
    • 1970-01-01
    • 2020-07-30
    相关资源
    最近更新 更多