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

3

我想了解如何改变一些按钮的行为才能实现以下功能:

我希望点击一下就会弹出一个菜单。或者当你拖动同一个按钮并将其放入另一个按钮中时,它们之间会“连线”。

这是一个示例: enter image description here 这个想法是将那些“插孔”按钮与任何其他“输入”按钮连接起来。

我使用 Qt Designer,发现只有接受拖放属性列在按钮属性中,但我无法使其工作。 信号/插槽没有关于拖放的信息。

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

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

更新:我尝试使用Qt Designer和“Promoted widgets”选项来实现。这使我可以创建独立的类文件并重新实现一些元素。我已经通过将PushButton提升为“DragButton”并为其创建一个类进行了测试:

from PyQt4 import 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 目前,我可以拖动这个按钮,并将其放入另一个具有“acceptDrops”属性设置为true的相同类型的按钮中。但是,我仍然希望限制某些按钮的拖动(可能是通过在UpdateUi方法中设置主文件),因为有些按钮只用于接受拖放。
更新2:现在我正在尝试编写一个类,该类绘制连接这些按钮的线条或“电线”。
我正在尝试在graphicsView中以它们的位置为参考在两个小部件(两个pushbutton)之间画一条线。但是当我尝试时,线条会在错误的位置绘制。我还尝试使用mapToGlobal或mapToParent等函数,但结果仍然不正确。
在同一个类中,我有另一个用鼠标绘制线条的方法,它运行良好。我把它当作参考或示例,但似乎事件位置具有不同的坐标系。好吧,我不知道为什么会发生这种情况。
按钮和graphicsview位于一个小部件内,该小部件也位于窗口内。
以下是这个类,我们正在谈论的方法是from PyQt4 import 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
我认为你是对的,也许我需要一个下拉菜单,但作为附加功能,我需要拖动此按钮,然后将其放置到表单上其他位置的任何类似按钮中。这些按钮的任何拖放操作都会画出连接它们的线条……或者在下拉菜单中的任何选择也会做同样的事情。因此,在这些小部件的任何“线连接”(或断开连接)之后,我还需要一种方式来捕获该事件以编写适当的操作。 - Mr_LinDowsMac
@Mailerdaimon 像这样:[链接]https://www.dropbox.com/s/li5xuz574r91cgz/mixerwindow.png?dl=0 这只是一个例子,GUI将会有比那更多的项目。 - Mr_LinDowsMac
如果您将图片上传到公共服务,我可以将其添加到您的帖子中。 - Mailerdaimon
@Mailerdaimon 我在这里上传了它:http://i.minus.com/iwUc5wK0PNXlu.png - Mr_LinDowsMac
4个回答

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。

我尝试在__init__方法中将该属性设置为按钮。显然没有起作用: 异常 "未处理的AttributeError" 'QPushButton'对象没有'setDragDropMode'属性现在,我不确定是否可以通过这种方式实现自定义属性或行为。也许我需要重新实现纯代码而不是使用设计师:S - Mr_LinDowsMac
QPushButtons没有setDragDropMode()属性。无论是由您创建还是由QtDesigner-pyuic创建。我编辑了帖子以显示如何使用自定义小部件。您将不得不创建自己的DragableButton,并使其可拖动。请查看此处 - Smasho
我使用pyuic4编译了ui文件,并且我看到了导入的推广小部件。然而,当我运行运行应用程序的文件时,我遇到了一个ImportError:ImportError: No module named dragbutton。我是否还需要在“主”文件中导入它,或者我需要对pyuic4做些什么? - Mr_LinDowsMac
你需要创建一个名为dragbutton.py的文件,并在其中定义你自己的小部件(你自定义的DragButton必须继承QPushButton,并且必须在dragbutton.py中定义)。 - Smasho

1
我的感觉是你可以尝试使用标准的QWidget来实现这个,但使用QGraphicsScene/QGraphicsView API会更容易一些。
另外,请注意你可以使用QGraphicsProxyWidget将一个QWidget嵌入到QGraphicsScene中。

0

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

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

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

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++编码人员,但我可以提供基本示例。

下拉菜单。您可以创建一个简单的QWidget,其中包含一些mouseEvents和将其设置为按钮的子级的菜单,并设置其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 文档非常棒。

祝你好运!


我写的绘制线条的代码可能看起来很复杂。但实际上并不是这样。我所写的是计算按钮右侧中间位置。如果您了解得更多,它就会变得非常合理。 - user4516901
是的,我想是这样。我认为使用设计师是快速开发具有许多控件的界面的最佳方法。 - Mr_LinDowsMac
好的,是时候读一下《Python和Qt快速编程GUI》这本书了 xD - Mr_LinDowsMac
1
@Mr_LinDowsMac,您可以将QtDesigner表单与自己的代码结合使用。在PyQt中实现的方法是通过子类化pyuic工具生成的类。 - Smasho
1
我同意,完全在QtDesigner中无法完成这个任务。通过子类化生成的表单,您只能获得您定义的GUI,而没有行为(最多可以获得一些信号槽连接)。 - Smasho
显示剩余14条评论

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