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

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

I want the menu to appear with one click. Or, when you drag the same button, you can drop it into another, and this will "draw" a line connecting them.

Here is an example: enter image description here The idea is to connect these jack buttons to any other input buttons.

I used the Qt constructor, and I understand that only acceptDrops is specified in the button properties, but I cannot get it to work. Signals / slots do not list something about drag and drop.

So, I think the only way to do this is to create a "Custom widget" or "redefine" the button by code. Perhaps the same with signals / slots

What is the best approach for this if I don't want to modify the file generated by pyuic?

UPDATE The approach I tried is to use the Qt constructor and the Advanced Widgets option. This allows me to create separate class files and override some elements. I already tested by pushing PushButton to "DragButton" and creating a class for it:

from import PyQt4 QtGui, QtCore

DragButton 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' % (ex(), ey())) print mimeData.text() # let 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 (ie 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 have tips and excerpts from this post: PyQt4 - Drag and Drop

At this point, I can drag this button and transfer it to another type of the same type with the acceptDrops property set to true in the Qt design. However, I still want to limit the drag and drop of some buttons (perhaps by setting the UpdateUi method in the main file), because some will only be for accepting drops

UPDATE 2: Now I'm trying to write a class that draws lines or “wires” connecting these buttons.

I am trying to draw a line between two widgets (two buttons) in a graphic element with their positions as a link. But when I try, the line is drawn in the wrong place. I also tried using features 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 fine. I took this as a reference or example, but it seems that the position of the events has a different coordinate system. Well, I don’t know why this is happening.

The buttons and graphical representation are inside the widget, which is also inside the window.

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

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

+2
qt pyqt pyqt4 qt-designer
source share
4 answers

To add code to the user interface created using QtDesigner, you must generate the .py file using pyuic :

 pyuic myform.ui -o ui_myform.py 

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

The generated file will have class Ui_myForm(object) (named in the name of the main widget), with the def setupUi(self, myForm) inside. One of the ways this can be used is to create your own class MyForm (in a separate file) that inherits Ui_myForm and some other Qt classes, such as 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) 

Widget names are the ones you installed in QtDesigner, but it's easy to check ui_myform.py to see the available widgets and names.

To use the custom widget in QtDesigner, you can right-click it and go to the "Promote" section .... There you need to enter:

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

Click "Add" and then "Promote."

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

 from mypushbutton import MyPushButton 

In addition, you will see that the generated code used MyPushButton instead of QPushButton

+2
source share

I feel that you can try to achieve this with a standard QWidget , but it would be easier to use the QGraphicsScene / QGraphicsView API.

Also note that you can insert a QWidget into a QGraphicsScene using the QGraphicsProxyWidget .

+1
source share

If you want to draw a line between the buttons, this means that you need to override the "paintEvent" of the background widget (possibly the parent widget of all the motions), as you said, this is NOT the best practice like that. Instead, you need to use QGraphicsWidget, to draw a line you need to use QGraphicsLineItem. It has the following member functions:

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

In the PyQt4 installation folder, there should be examples of the \ graphicsview \ diagramscene folder naming conventions that you take for reference.

0
source share

You need to use QDropEvent, I know that this is not a good answer, but just create a QDropEvent function and check the reset button in this function.

If firstButton falls on secondButton, painter->drawLine(firstButton.pos(), secondButton.pos()); You can use other points to draw a line. Or you can use event->source() for the draggable button. You may need to define QPen with some settings. When I say what other points to use, I mean like firstButton.boundingRect().topRight().x(), firstButton.boundingRect.bottomRight().y() - firstButton.boundingRect.height() / 2

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

Sorry, this code is C ++ pseudo-code, but you can easily adapt it to Python.

Example:

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

There are other drag and drop events. You can change the target color, for example, by dragging it. See http://doc.qt.io/qt-5/dnd.html

I can try to help with the collision detection code if you need it. I do not know if there is an even better way. May be. I am basically a C ++ encoder, but I can give basic examples.

And a drop down menu. You can create a simple QWidget with your menu with some mouseEvents and make it a child of the button and set the y position so that it appears under the button. For example (again, C ++, sorry):

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

You can hide or show it with mouseReleaseEvent. Just be sure to hide it in your dropEvent function, so when you drag it, it doesn't show.

Edit: Let me show you a simple collision detection code in C ++, but easy to adapt to Python.

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

If you need a simpler solution. Just subclass the buttons you're about to pounce on and add drag and drop events to your class. That would be simpler and shorter. I think dropEvent should work in a subclass too. I have not tried.

Edit: if you ask how to do everything using only Qt Designer, you cannot. You need to write a code. You cannot develop programs with Qt Designer, this is just to simplify ui. You can do software without Qt Designer, but you cannot do software only with Qt Designer. You will need to learn a little Python programming and a little PyQt for such tasks. But both Python and Qt are light enough to get them in a short time, and the Qt documentation is amazing.

Good luck

0
source share

All Articles