拖放按钮和下拉菜单 PyQt/Qt 设计师

Drag n Drop Button and Drop-down menu PyQt/Qt designer

我想知道 "best practice" 更改某些按钮的行为以执行以下操作:

我想通过点击显示一个菜单。或者,当您拖动同一个按钮时,您可以将它放到另一个按钮中,这将 "draw" 一条连接它们的线。

这是一个例子: 这个想法是将这些 "jack" 按钮连接到任何其他 "input" 按钮。

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

所以我认为唯一的方法是通过代码创建一个 "Custom Widget" 或 "reimplementing" 按钮。也许与 Signals/Slots

相同

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

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

从 PyQt4 导入 QtGui、QtCore

class 拖动按钮(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()

我得到了一些提示并从中摘录了一些片段 post: PyQt4 - Drag and Drop

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

更新 2: 现在我正在尝试编写 class 绘制线条或 "wires" 连接这些按钮。

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

按钮和图形视图在 Widget 内,Widget 也在 Window.

这里是class,我们说的方法是 从 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 )

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

我的感觉是,您可以尝试使用标准 QWidget 来实现此目的,但使用 QGraphicsScene/QGraphicsView API.[=17 会更容易=]

另请注意,您可以使用 QGraphicsProxyWidget.

QGraphicsScene 中嵌入 QWidget

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

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

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

您需要使用 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.
 }

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

编辑:如果您询问如何仅使用 Qt Designer 来完成所有这些操作,那么您做不到。你需要写一些代码。你不能用 Qt Designer 开发程序,它只是为了让 ui 更容易。没有 Qt Designer 可以制作软件,但是 Qt Designer 无法制作软件。您需要学习一些 Python 编程知识和一些 PyQt 才能完成此类任务。但是 Python 和 Qt 都很容易在短时间内掌握它们,而且 Qt 文档非常棒。

祝你好运!

为了向使用 QtDesigner 生成的 UI 添加代码,您必须使用 pyuic:[=16 生成 .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 Class,如 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 中使用自定义小部件,您可以右键单击该按钮,然后转到升级到...。在那里你必须输入:

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

单击添加,然后单击升级

当你运行 pyuic时,它会在ui_myform.py

末尾添加这一行
from mypushbutton import MyPushButton

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