【问题标题】:Drag n Drop Button and Drop-down menu PyQt/Qt designer拖放按钮和下拉菜单 PyQt/Qt 设计器
【发布时间】:2015-02-01 00:12:41
【问题描述】:

我想知道更改某些按钮的行为以执行以下操作的“最佳做法”:

我想通过点击出现一个菜单。或者,当您拖动同一个按钮时,您可以将其放在另一个按钮中,这将“绘制”一条连接它们的线。

这是一个例子: 这个想法是将这些“插孔”按钮连接到任何其他“输入”按钮。

我正在使用 Qt 设计器,我意识到按钮属性仅列出了“acceptDrops”属性,但我无法使其工作。 Signals/Slots 没有列出有关拖放的内容。

所以我认为唯一的方法是创建“自定义小部件”或通过代码“重新实现”按钮。信号/插槽可能也是一样

如果我不想修改 pyuic 生成的文件,最好的方法是什么?

更新:我尝试的方法是使用 Qt 设计器和“Promoted widgets”选项。这允许我创建单独的类文件并重新实现一些元素。我已经通过将 PushButton 提升为“DragButton”进行了测试,并为它创建了一个类:

从 PyQt4 导入 QtGui、QtCore

类 DragButton(QtGui.QPushButton):

def __init__(self, parent):
     super(DragButton,  self).__init__(parent)
     self.allowDrag = True

def setAllowDrag(self, allowDrag):
    if type(allowDrag) == bool:
       self.allowDrag = allowDrag
    else:
        raise TypeError("You have to set a boolean type")

def mouseMoveEvent(self, e):
    if e.buttons() != QtCore.Qt.RightButton:
        return

    if self.allowDrag == True:
        # write the relative cursor position to mime data
        mimeData = QtCore.QMimeData()
        # simple string with 'x,y'
        mimeData.setText('%d,%d' % (e.x(), e.y()))
        print mimeData.text()

        # let's make it fancy. we'll show a "ghost" of the button as we drag
        # grab the button to a pixmap
        pixmap = QtGui.QPixmap.grabWidget(self)

        # below makes the pixmap half transparent
        painter = QtGui.QPainter(pixmap)
        painter.setCompositionMode(painter.CompositionMode_DestinationIn)
        painter.fillRect(pixmap.rect(), QtGui.QColor(0, 0, 0, 127))
        painter.end()

        # make a QDrag
        drag = QtGui.QDrag(self)
        # put our MimeData
        drag.setMimeData(mimeData)
        # set its Pixmap
        drag.setPixmap(pixmap)
        # shift the Pixmap so that it coincides with the cursor position
        drag.setHotSpot(e.pos())

        # start the drag operation
        # exec_ will return the accepted action from dropEvent
        if drag.exec_(QtCore.Qt.LinkAction | QtCore.Qt.MoveAction) == QtCore.Qt.LinkAction:
            print 'linked'
        else:
            print 'moved'

def mousePressEvent(self, e):
    QtGui.QPushButton.mousePressEvent(self, e)
    if e.button() == QtCore.Qt.LeftButton:
        print 'press'
        #AQUI DEBO IMPLEMENTAR EL MENU CONTEXTUAL

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

def dropEvent(self, e):
    # get the relative position from the mime data
    mime = e.mimeData().text()
    x, y = map(int, mime.split(','))

        # move
        # so move the dragged button (i.e. event.source())
    print e.pos()
        #e.source().move(e.pos()-QtCore.QPoint(x, y))
        # set the drop action as LinkAction
    e.setDropAction(QtCore.Qt.LinkAction)
    # tell the QDrag we accepted it
    e.accept()

我得到了一些提示,并从这篇文章中获取了 sn-ps: PyQt4 - Drag and Drop

此时,我可以拖动此按钮,并将其放到另一个相同类型中,该类型在 Qt 设计器中将“acceptDrops”属性设置为 true。 但是,我仍然想限制某些按钮的拖动(可能通过使用 UpdateUi 方法在主文件中设置),因为有些按钮仅用于接受 drop

更新 2: 现在我正在尝试编写一个类来绘制连接这些按钮的线条或“电线”。

我正在尝试在两个小部件(两个按钮)之间画一条线,并将它们的位置作为参考。但是当我尝试时,这条线画错了地方。我也尝试使用 mapToGlobal 或 mapToParent 等函数,但结果不同,但仍然错误。 在同一个类中,我有另一种用鼠标画线的方法,并且工作正常。我把它当作参考或示例,但似乎事件位置具有不同的坐标系。好吧,我不知道为什么会这样。

按钮和图形视图位于 Widget 内部,Widget 也在 Window 内部。

这里是类,我们说的方法是 从 PyQt4 导入 QtGui,QtCore

class WiringGraphicsView(QtGui.QGraphicsView):

    def __init__(self, parent):
        QtGui.QGraphicsView.__init__(self, parent)
        self.setScene(QtGui.QGraphicsScene(self))
        self.setSceneRect(QtCore.QRectF(self.viewport().rect()))

    def mousePressEvent(self, event):
        self._start = event.pos()

    def mouseReleaseEvent(self, event):
        start = QtCore.QPointF(self.mapToScene(self._start))
        end = QtCore.QPointF(self.mapToScene(event.pos()))
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
        pen = QtGui.QPen(brush, 2)
        line = QtGui.QGraphicsLineItem(QtCore.QLineF(start, end))
        line.setPen(pen)
        self.scene().addItem( line )

    def paintWire(self, start_widget,  end_widget):
        start_position = QtCore.QPointF(self.mapToScene(start_widget.pos()))
        end_position = QtCore.QPointF(self.mapToScene(end_widget.pos()))
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
        pen = QtGui.QPen(brush, 2)
        line = QtGui.QGraphicsLineItem(QtCore.QLineF(start_position, end_position))
        line.setPen(pen)
        self.scene().addItem( line )

如果有更好的实现方法,请告诉我。

【问题讨论】:

  • 您能否详细说明:“我希望通过简单的点击来显示一个菜单。”和“当你拖动同一个按钮时,你可以把它放在另一个按钮上,这将“画”一条连接它们的线。“你想点击哪里?在按钮上?为什么不使用下拉菜单? “将按钮放在另一个上”是什么意思?按钮应该放在哪里?也许你可以画一个线框?
  • 我认为你是对的,也许我需要一个下拉菜单,但作为一个附加功能,我需要拖动这个按钮,然后将它放到位于其他位置的任何其他类似按钮中表格。这些按钮的任何拖放都会画一条连接它们的线......或者下拉菜单中的任何选择都会做同样的事情。因此,在这些小部件的任何“线路连接”(或断开连接)之后,我还需要一种方法来捕获该事件以为其编写适当的操作。
  • @Mailerdaimon 类似这样的东西:[link]dropbox.com/s/li5xuz574r91cgz/mixerwindow.png?dl=0 这只是一个例子,GUI 将有比这更多的项目。
  • 如果您将图片上传到公共服务,我可以将其添加到您的帖子中。
  • @Mailerdaimon 我在这里上传了它:i.minus.com/iwUc5wK0PNXlu.png

标签: qt python-2.7 pyqt pyqt4 qt-designer


【解决方案1】:

为了将代码添加到使用 QtDesigner 生成的 UI,您必须使用 pyuic 生成一个 .py 文件:

pyuic myform.ui -o ui_myform.py

此 ui_myform.py 文件包含生成的代码,您不应编辑,因此稍后您可以使用 QtDesigner 更改您的 .ui 文件,重新运行 pyuic,并在不丢失的情况下更新 ui_myform.py任何工作。

生成的文件将有一个class Ui_myForm(object)(以您的主小部件名称命名),其中包含一个def setupUi(self, myForm) 方法。可以使用的一种方法是创建自己的class MyForm(在单独的文件上),它将继承 Ui_myForm 和其他一些 Qt 类,如 QWidget 或 QDialog:

myform.py:

from ui_myform import Ui_myForm
from PyQt4.QtGui import QDialog

class MyForm(QDialog, Ui_myForm):

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

        self.setupUi(self)    #here Ui_myForm creates all widgets as members 
                              #of this object.
                              #now you can access every widget defined in 
                              #myForm as attributes of self   

        #supposing you defined two pushbuttons on your .UI file:
        self.pushButtonB.setEnabled(False)

        #you can connect signals of the generated widgets
        self.pushButtonA.clicked.connect(self.pushButtonAClicked)



    def bucar_actualizaciones(self):
        self.pushButtonB.setEnabled(True)

小部件的名称是您在 QtDesigner 上设置的名称,但很容易检查 ui_myform.py 以查看可用的小部件和名称。

为了在 QtDesigner 中使用自定义小部件,您可以右键单击该按钮,然后转到 Promote to...。您必须在此处输入:

  • 基类名:例如QPushButton
  • 提升的类名:MyPushButton(这必须是您的自定义小部件的类名)
  • 头文件:mypushbutton.h。这将由 pyuic 转换为 .py。

点击添加,然后点击推广

当你运行 pyuic 时,它会在 ui_myform.py 的末尾添加这一行

from mypushbutton import MyPushButton

另外,您会看到生成的代码使用了 MyPushButton 而不是 QPushButton

【讨论】:

  • self.btn_draggable.setDragDropMode(QtGui.QAbstractItemView.DragDrop) 我试图在 init 方法中将该属性设置为 Button。显然没有用:Exception "unhandled AttributeError" 'QPushButton' object has no attribute 'setDragDropMode' 现在,我不确定自定义属性或行为是否可以通过这种方式完成。也许我需要用纯代码重新实现,而不是使用设计器:S
  • QPushButtons 没有 setDragDropMode() 属性。无论它们是由您还是由 QtDesigner-pyuic 创建的。我编辑了帖子以展示如何使用自定义小部件。您必须创建自己的 DragableButton,并使其可拖动。检查this
  • 我用pyuic4编译了ui文件,看到了导入的promote widget。但是,当我运行运行应用程序的文件时,我得到了 ImportError: ImportError: No module named dragbutton 我是否必须在“主”文件中也导入它,或者我必须对 pyuic4 做一些事情?
  • 您必须创建一个名为 dragbutton.py 的文件,其中包含您的自定义小部件(继承 QPushButton 的自定义 DragButton,必须在 dragbutton.py 中定义)
【解决方案2】:

我的感觉是您可以尝试使用标准的QWidget 来实现这一点,但使用QGraphicsScene/QGraphicsView API 会更容易。

另外,请注意,您可以使用 QGraphicsProxyWidgetQWidget 嵌入到 QGraphicsScene 中。

【讨论】:

    【解决方案3】:

    如果你想在按钮之间画一条线,这意味着你需要重新实现背景小部件的“paintEvent”(可能是所有子小部件的父小部件),正如你所说,这不是最好的做法所以。相反,你需要使用QGraphicsWidget,对于画线,你需要使用QGraphicsLineItem。它有以下成员函数:

    setAcceptDrops
    dragEnterEvent ( QGraphicsSceneDragDropEvent * )
    dragLeaveEvent ( QGraphicsSceneDragDropEvent * )
    dragMoveEvent ( QGraphicsSceneDragDropEvent * )
    

    在PyQt4的安装文件夹中,应该有一个文件夹命名为examples\graphicsview\diagramscene,你可以参考一下。

    【讨论】:

      【解决方案4】:

      您需要使用 QDropEvent,我知道这不是一个很好的答案,但只需创建一个 QDropEvent 函数并在该函数中检查放置的按钮。

      如果 firstButton 放在 secondButton 上,painter->drawLine(firstButton.pos(), secondButton.pos()); 您可以使用其他点来绘制线条。或者您可以使用event->source() 作为拖动按钮。您可能需要使用一些设置定义 QPen。当我说其他要使用的点时,我的意思是firstButton.boundingRect().topRight().x(), firstButton.boundingRect.bottomRight().y() - firstButton.boundingRect.height() / 2

      见:http://doc.qt.io/qt-5/qdropevent.html

      抱歉,此代码是伪 C++ 代码,但您可以轻松地将其改编为 Python。

      例子:

       void MainWindow::dropEvent(QDropEvent *event)
       {
           if(event->pos() == somePoint) //somePoint should be inside target's boundingRect, you need to write basic collision detection
                painter->drawLine(event->source().pos(), event->pos()); //You might use other points
       }
      

      还有其他拖放事件。您可以更改目标的颜色,例如,如果将其拖过它。见http://doc.qt.io/qt-5/dnd.html

      如果您需要,我可以尝试提供碰撞检测代码。不过不知道有没有更好的办法。可能有。我主要是 C++ 编码员,但我可以提供基本示例。

      还有下拉菜单。您可以使用带有一些 mouseEvents 的菜单创建一个简单的 QWidget,并使其成为按钮的子级,并设置它的 y 位置,使其显示在按钮下方。例如(再次,C++,对不起):

       dropDownMenu->setParent(someButton);
       dropDownMenu->setPos(someButton.x(), someButton.y() + someButton.boundingRect().height());
      

      您可以使用 mouseReleaseEvent 隐藏或显示它。只需确保将其隐藏在您的 dropEvent 函数中,这样当您拖放它时,它就不会显示。

      编辑:让我向您展示一个简单的 C++ 碰撞检测代码,但很容易适应 Python。

       if(event->pos() > target.boundingRect().topLeft().x() && event->pos() < target.topRight.x() && event->pos() > target.boundingRect().topRight() && event->pos() < target.boundingRect().bottomRight()) {
            //It's on the button.
       }
      

      如果您想要更简单的解决方案。只需将要放置的按钮子类化,然后在它们的类中添加拖放事件。这会更容易,也更短。我认为 dropEvent 也应该在子类中工作。我没试过。

      编辑:如果您询问如何仅使用 Qt Designer 来完成所有这些操作,您不能。你需要写一些代码。你不能用Qt Designer开发程序,它只是为了让用户界面更容易。你可以不使用 Qt Designer 制作软件,但你不能使用 Qt Designer 制作软件。对于这样的任务,您需要学习一点 Python 编程和一点 PyQt。但是 Python 和 Qt 都很容易在短时间内掌握它们的窍门,而且 Qt 文档非常棒。

      祝你好运!

      【讨论】:

      • 查看答案末尾的编辑。这可能是你真正的答案。
      • 我已经意识到不能用 Qt 设计器来做这个。我的问题也是关于通过使用 ui 生成的文件来组合 Qt 设计器并用它实现自定义小部件代码。
      • 我已经给你一个彻底的答案了。我不知道更多,对不起。
      • @Mr_LinDowsMac,您可以将 QtDesigner 表单与您自己的代码结合起来。使用 PyQt 的方法是子类化 pyuic 工具生成的类
      • 我同意,在 QtDesigner 中无法完全做到这一点。对生成的表单进行子类化,您将获得您定义的 GUI,没有行为(您可以获得的大多数行为是一些信号槽连接)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-07
      • 1970-01-01
      • 1970-01-01
      • 2011-08-07
      • 2016-11-20
      相关资源
      最近更新 更多