问题
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:
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.
回答1:
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
回答2:
My feeling is that you could try to achieve this with standard QWidget
, but it would be easier to use QGraphicsScene
/QGraphicsView
API.
Also, note that you can embed a QWidget
in a QGraphicsScene
, using QGraphicsProxyWidget.
回答3:
If you want to draw a line between the buttons, it means you need to reimplement the "paintEvent" of the background widget (Possibly the parent widget of all subwidgets), as you said, this is NOT the best practice to do so. Instead, you need to use QGraphicsWidget, for drawing line, you need to use QGraphicsLineItem. It have the following member functions:
setAcceptDrops
dragEnterEvent ( QGraphicsSceneDragDropEvent * )
dragLeaveEvent ( QGraphicsSceneDragDropEvent * )
dragMoveEvent ( QGraphicsSceneDragDropEvent * )
In the PyQt4 installation folder, there should exist a folder naming examples\graphicsview\diagramscene, which you take it for a reference.
回答4:
You need to use a QDropEvent, I know this is not a very good answer but just create a QDropEvent function and in that function check the dropped button.
If firstButton's dropped on secondButton, painter->drawLine(firstButton.pos(), secondButton.pos());
You can use other points for drawing the line. Or you can use event->source()
for the dragged button. You might need to define a QPen with some settings. When I say 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 pseudo C++ code, but you can easily adapt it to Python.
Example:
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
}
There are other drag and drop events also. You can change the target's color for example if dragged over it. See http://doc.qt.io/qt-5/dnd.html
I can try to help with collision detection code if you need it. I don't know if there's better way though. There may be. I'm mostly a C++ coder, but I can provide basic examples.
And the dropdown 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 of it so it shows 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 make sure to hide it back in your dropEvent function, so when you drag and drop it, it doesn't show.
Edit: Let me show you a simple collision detection code in C++, but easily adaptable 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's on the button.
}
If you want a simpler solution. Just subclass the buttons you're going to drop onto and add the drag and drop events in their class. This would be easier, and shorter. I think dropEvent should work in a subclass too. I haven't tried it.
Edit: If you're asking how to do all of them using only Qt Designer, you can't. You need to write some code. You can't develop programs with Qt Designer, it's just for making the ui more easily. You can make software without Qt Designer, but you can't make software with only Qt Designer. You'll need to learn a bit of Python programming and a bit of PyQt for tasks like this. But both Python and Qt are easy enough to get the hang of them in a short time, and Qt documentation is marvellous.
Good luck!
来源:https://stackoverflow.com/questions/28258050/drag-n-drop-button-and-drop-down-menu-pyqt-qt-designer