拖动下拉按钮和下拉菜单PyQt / Qt设计器

时间:2015-02-01 00:12:41

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

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

我想点击一下即可显示一个菜单。或者当您拖动同一个按钮时,您可以将其拖放到另一个按钮中,这将“绘制”连接它们的线。

这是一个例子: enter image description here 我们的想法是将这些“插孔”按钮连接到任何其他“输入”按钮。

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

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

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

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

从PyQt4导入QtGui,QtCore

class 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()

我得到了一些提示并从这篇文章中摘取了片段: PyQt4 - Drag and Drop

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

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

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

按钮和图形视图位于窗口小部件内,窗口小部件也在窗口内。

这是课堂,我们谈论的方法是 来自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 )

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

4 个答案:

答案 0 :(得分:2)

为了向使用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中使用自定义窗口小部件,您可以右键单击该按钮,然后转到提升为... 。你必须输入:

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

点击添加,然后点击推广

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

from mypushbutton import MyPushButton

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

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

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

在PyQt4安装文件夹中,应该存在一个命名为examples \ graphicsview \ diagramscene的文件夹,您可以将其作为参考。

答案 3 :(得分:0)

你需要使用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开发程序,它只是为了更容易制作ui。您可以在没有Qt Designer的情况下制作软件,但是您无法使用 Qt Designer制作软件。您需要学习一些Python编程和一些PyQt来完成这样的任务。但是Python和Qt都很容易在短时间内掌握它们,Qt文档非常棒。

祝你好运!