Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
301 views
in Technique[技术] by (71.8m points)

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

I would like to know the "best practice" to change the behavior of some buttons to do the following:

I want with a click to appear a menu. Or when you drag this same button you could drop it in another and this will "draw" a line connecting them.

Here is an Example: enter image description here The idea is to connect those "jack" buttons to any other "input" buttons.

I was using the Qt designer and I realize that the buttons properties only the "acceptDrops" property is listed, but I can't make it work. Signals/Slots doesn't list something about dragging or dropping.

So I think that the only way to do it is creating a "Custom Widget" or "reimplementing" a button by code. Maybe the same thing with Signals/Slots

What is the best approach to do so if I don't want to modify the pyuic generated file?

UPDATE: The approach that I tried is by using Qt designer and the "Promoted widgets" option. That allows me to create separate class files and reimplement some elements. I already tested by promoting a PushButton to a "DragButton" and created a class for it:

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

I got some hints and take snippets from this post: PyQt4 - Drag and Drop

At this point I'm able to drag this button, and drop it into another of the same type that have the "acceptDrops" property set true in Qt designer. However, I still want to restrict dragging of some buttons (perhaps by setting at the main file with UpdateUi method) because some will be just for accepting drops

UPDATE 2: Now I'm trying to write a class which paint lines or "wires" connecting those buttons.

I'm trying to draw a line between two widgets (two pushbuttons) into a graphicsView with their positions as reference. But when I try, the line is drawn in a wrong place. I also tried using functions like mapToGlobal or mapToParent with different results, but still wrong. In the same class I have another method that draws lines with the mouse, and works ok. I was taking it like a reference or example, but it seems that the events position has a different coordinate system. Well, i don't know why is happening this.

The buttons and the graphicsview are inside a Widget, which is also inside a Window.

Here it is the class, the method that we are talking about is 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 )

If is there any better way to implement this, please tell me.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

In order to add code to a UI generated with QtDesigner, you must generate a .py file using pyuic:

pyuic myform.ui -o ui_myform.py

This ui_myform.py file, contains generated code that you should not edit, so later you can change your .ui file with QtDesigner, re-run pyuic, and get ui_myform.py updated without loosing any work.

The generated file will have a class Ui_myForm(object) (named after on your main widget's name), with a def setupUi(self, myForm) method inside. One way this can be used, is by creating your own class MyForm (on a separate file) which will inherit Ui_myForm, and some other Qt Class, like QWidget or 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)

The names of the widgets are the one you set on QtDesigner, but is easy to inspect ui_myform.py in order to see the available widgets and names.

In order to use a custom widget in QtDesigner, you can right-click the button, and go to Promote to.... There you'l have to enter:

  • Base class name: QPushButton for example
  • Promoted class name: MyPushButton (this must be the name of the class of your custom widget)
  • Header file: mypushbutton.h. This will be converted to .py by pyuic.

Click Add and then Promote.

When you run pyuic, it will add this line at the end of ui_myform.py

from mypushbutton import MyPushButton

Also, you'll see that the generated code used MyPushButton instead of QPushButton


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...