PyQt中移动/拖动QPushButton

9

我真的很难想出一种方法来做到这一点。比如我在小部件窗口中简单地实现了一个按钮:

self.button = QPushButton("Drag Me", self)

我可以使用 self.button.move(x,y) 将其初始化点移动到父窗口的任意位置,并且可以通过 mousePressEvent(self, e)e.x()e.y() 获得鼠标事件,从而使按钮随着鼠标点击而移动。但是我似乎无法将所有这些组合成一个拖放框架。
澄清一下:在了解了“真正”的拖放含义后,我不需要它。我只想能够用鼠标移动小部件,就像在冰箱上移动磁铁一样。

1
@Eric在他的回答中提出了一个非常好的观点。您能否澄清一下这个问题,是想要真正的拖放事件...还是只是想用鼠标移动按钮? - jdi
1
根据你的意图,我建议你研究一下QGraphicsView框架。你想要实现的虚拟磁板非常容易通过该框架实现。 - Eric Hulser
2个回答

19

这里有一个可移动的按钮示例,仍然可以正常支持点击信号:

from PyQt4 import QtCore, QtGui


class DragButton(QtGui.QPushButton):

    def mousePressEvent(self, event):
        self.__mousePressPos = None
        self.__mouseMovePos = None
        if event.button() == QtCore.Qt.LeftButton:
            self.__mousePressPos = event.globalPos()
            self.__mouseMovePos = event.globalPos()

        super(DragButton, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() == QtCore.Qt.LeftButton:
            # adjust offset from clicked point to origin of widget
            currPos = self.mapToGlobal(self.pos())
            globalPos = event.globalPos()
            diff = globalPos - self.__mouseMovePos
            newPos = self.mapFromGlobal(currPos + diff)
            self.move(newPos)

            self.__mouseMovePos = globalPos

        super(DragButton, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if self.__mousePressPos is not None:
            moved = event.globalPos() - self.__mousePressPos 
            if moved.manhattanLength() > 3:
                event.ignore()
                return

        super(DragButton, self).mouseReleaseEvent(event)

def clicked():
    print "click as normal!"

if __name__ == "__main__":
    app = QtGui.QApplication([])
    w = QtGui.QWidget()
    w.resize(800,600)

    button = DragButton("Drag", w)
    button.clicked.connect(clicked)

    w.show()
    app.exec_()
mousePressEvent中,我记录了初始的起始位置和一个将在拖动过程中更新的位置。
mouseMoveEvent中,我获取小部件从被点击位置到实际原点的正确偏移量,以便移动是准确的。
mouseReleaseEvent中,我检查总体移动是否大于至少一点点。如果是,那么它是一个拖动事件,我们忽略常规事件以不产生“clicked”信号。否则,我们允许正常的事件处理程序产生点击。

这应该足够让我在此期间思考了。谢谢! - RodericDay
我曾犹豫不决,因为我最喜欢的答案是使用QGraphicsScene,但这个解决方案才是对我提出的确切问题更好的答案。给你! - RodericDay
@jdi 我正在尝试在标签移动时使用setStyleSheet更改其颜色。我在mouseMoveEvent函数中执行此操作,但问题是当我松开并移动另一个按钮时,原始按钮会移回其原始位置。我无法弄清楚为什么setStyleSheet会导致这种行为。 - Shock-o-lot
@Shock-o-lot,使用样式表会导致按钮移回其原始位置的可能性非常小。您确定这是因素吗?当您禁用setStyleSheet调用时,您是否注意到它可以正常工作? - jdi
@jdi 我在这里发布了一个单独的问题:https://stackoverflow.com/questions/59221822/pyside-movable-labels-snap-back-to-original-position-when-released-trying-to-m/59222559#59222559。 - Shock-o-lot
显示剩余4条评论

2
这取决于你想做什么。如果你想要实际执行“拖放(Drag & Drop)”操作,你的方法是错误的。你所做的只是在按钮的父级内部移动按钮的X、Y坐标。它从未实际调用任何拖放事件,这些事件完全不同。
你应该阅读这里的拖放文档:

http://doc.qt.nokia.com/4.7-snapshot/dnd.html
http://qt-project.org/doc/qt-5.0/qdrag.html

在mousePressEvent中,你需要创建一个新的QDrag对象并执行它,而不是移动按钮。你可以使用QPixmap::grabWidget方法对按钮进行快照,然后使用QDrag::setPixmap方法将其分配给QDrag实例,使其看起来像你的按钮。
即使你只是想在父窗口中移动小部件,我建议你使用这个框架,并接受按钮的放置事件。这样你就不会触发大量不必要的重绘。

你知道在哪里可以找到一个好的示例来跟随吗?我不介意从拖动一个彩色矩形或其他东西开始。对于某些原因,"QDrag"实例的想法对我来说过于复杂,就好像它的目的是将文件夹拖入目录之类的东西。 - RodericDay
是的,这就是它的主要观点。我想问题是,你的目标是什么? - Eric Hulser
我的终极目标是通过导入*.bmp文件来模拟游戏棋盘。短期内,我希望能够移动包含Pixmap的QLabel对象,就像在棋盘上移动棋子一样。但没有任何逻辑或自动放置区域。只是屏幕上的图像。我能想到的最好的类比是冰箱贴纸。我想要虚拟冰箱贴纸。 - RodericDay
2
啊,好的,那么是的,忽略 QDrag 的东西 - 你正在使用错误的小部件进行操作。对于你想要做的事情,你应该研究 QGraphicsView 框架。请参考这里的问题和答案:https://dev59.com/lGfWa4cB1Zd3GeqPlupc#12219643 - Eric Hulser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接