I'm trying to implement Drag&Drop in my application, but the dropEvent in the target widget is never called.
I searched this problem a lot but every solution I found involves overriding dragMoveEvent, which I did, but with no difference.
This example code for me is not working either, for the above reason:
Main window class:
class Win(QtWidgets.QWidget):
def __init__(self):
super(Win, self).__init__()
self.setGeometry(200, 300, 400, 200)
self.setLayout(QtWidgets.QHBoxLayout())
self.layout().addWidget(DragLabel())
self.layout().addWidget(DropTest())
Label to drag:
class DragLabel(QtWidgets.QLabel):
def __init__(self):
super(DragLabel, self).__init__()
self.setText("Drag me")
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.LeftButton:
return
mimeData = QtCore.QMimeData()
mimeData.setText("Test drop")
drag = QtGui.QDrag(self)
drag.setMimeData(mimeData)
dropAction = drag.exec(QtCore.Qt.CopyAction)
Widget to drop onto:
class DropTest(QtWidgets.QWidget):
def __init__(self):
super(DropTest, self).__init__()
self.setAcceptDrops(True)
def dragEnterEvent(self, e):
print("DragEnter")
e.accept()
def dragMoveEvent(self, e):
print("DragMove")
e.accept()
def dropEvent(self, e):
print("DropEvent")
position = e.pos()
print(position)
e.accept()
When I drag the label onto the other widget I see that both dragEnterEvent and dragMoveEvent are being called, but when I actually drop the label I get no message from the dropEvent function.
Plus, after closing the window the application will hang and won't quit.
I'm using PyQt 5.13.1 x86_64 installed with DNF in Fedora 31. Python version is 3.7.5 with no virtualenv.
As noted in a comment I had already answered the same question in this post, and I have tested it in a docker with fedora31 and it works correctly so after a discussion with the OP he pointed out it in a comment:
[PyQt5] was previously installed from
DNF, but I'm pretty sure setuptools also installed it from pip as a
dependency of my application.
The cause of the problem is that the OP is combining 2 ways of installation: dnf and pip, which each compiles with different versions of Qt, compilation flags, etc. that can cause certain functionalities to fail. The solution is to uninstall PyQt5 by both methods and reinstall only one of them.
Try it:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class DropTest(QtWidgets.QLabel): # - QWidget + QLabel
def __init__(self):
super(DropTest, self).__init__()
self.setAcceptDrops(True)
self.setText(" Accept Drops")
self.setStyleSheet("QLabel { background-color : #ccd; color : blue; font-size: 20px;}")
def dragEnterEvent(self, e):
# print("DragEnter")
e.accept()
def dragMoveEvent(self, e):
# print("DragMove")
e.accept()
def dropEvent(self, e):
# print("DropEvent")
# position = e.pos()
# print(position)
self.setText(e.mimeData().text()) # +++
e.setDropAction(Qt.MoveAction) # +++
e.accept()
class DragLabel(QtWidgets.QLabel):
def __init__(self):
super(DragLabel, self).__init__()
self.setText("Drag me")
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.LeftButton:
return
mimeData = QtCore.QMimeData()
mimeData.setText(self.text()) # ("Test drop")
drag = QtGui.QDrag(self)
drag.setMimeData(mimeData)
dropAction = drag.exec(QtCore.Qt.CopyAction)
class Win(QtWidgets.QWidget):
def __init__(self):
super(Win, self).__init__()
self.setGeometry(200, 300, 400, 200)
self.setLayout(QtWidgets.QHBoxLayout())
self.layout().addWidget(DragLabel())
self.layout().addWidget(DropTest())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Win()
w.show()
sys.exit(app.exec_())
Related
I am trying to build a hover Dialog but i am stuck at the interaction between QComboBox selection and QDialog's leaveEvent. it looks like when i try to click on combobox to select something, it triggers a leaveEvent which then hides my QDialog. Why is this happening? What can i try to ensure that the Dialog is only hidden when I move my mouse outside of the Dialog?
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class hoverDialog(QDialog):
def __init__(self, parent=None):
super().__init__()
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.v = QVBoxLayout()
self.combobox = QComboBox()
self.combobox.addItems(['Work-around','Permanent'])
self.textedit = QPlainTextEdit()
self.v.addWidget(self.combobox)
self.v.addWidget(self.textedit)
self.setLayout(self.v)
#self.setMouseTracking(True)
def leaveEvent(self, event):
self.hide()
return super().leaveEvent(event)
class Table(QWidget):
def __init__(self, parent=None):
super().__init__()
self.label4 = QLabel()
self.label4.setText("hover popup")
self.label4.installEventFilter(self)
self.checkbox1 = QCheckBox()
self.pushButton3 = QPushButton()
self.pushButton3.setText('Generate')
self.pushButton3.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
self.pushButton3.clicked.connect(self.buttonPressed)
self.hbox5 = QHBoxLayout()
self.hbox5.addWidget(self.checkbox1)
self.hbox5.addWidget(self.label4)
self.hbox5.addWidget(self.pushButton3)
self.vbox1 = QVBoxLayout()
self.vbox1.addLayout(self.hbox5)
self.setLayout(self.vbox1)
self.autoResolve = hoverDialog(self)
def eventFilter(self, obj, event):
if obj == self.label4 and event.type() == QtCore.QEvent.Enter and self.autoResolve.isHidden():
self.onHovered()
return super().eventFilter(obj, event)
def onHovered(self):
pos = QtGui.QCursor.pos()
self.autoResolve.move(pos)
self.autoResolve.show()
def buttonPressed(self):
if self.checkbox.isChecked():
print('do something..')
if __name__ == '__main__':
app = QApplication(sys.argv)
form = Table()
form.show()
app.exec_()
Looking at the sources, I believe that the origin of the problem is in Qt's behavior whenever a new popup widget is shown.
I cannot guarantee this at 100%, but in any case the simplest solution is to hide the widget only if the combo popup is not shown:
def leaveEvent(self, event):
if not self.combobox.view().isVisible():
self.hide()
Note that your approach is not really perfect: since the dialog could be shown with a geometry that is outside the current mouse position, it will not be able to hide itself until the mouse actually enters it. You should probably filter FocusOut and WindowDeactivate events also.
I'm setting up a new desktop widget to make my life easier at work and using QPropertyAnimation to make it pretty. Fading the app in and out doesn't seem to want to work and in typical coder fashion, it's brought my progress to a standstill.
I'm implementing QPropertyAnimation in a personalised class to make my life easier, but since it's not intially worked I've taken it back to the class code and it's still being pretty stubborn. So far I've tried.
class widget(QWidget):
def init(self):
self.setSize(QSize(300, 300))
self.setWindowOpacity(1)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
def paintEvent(self, event):
s = self.size()
qp = QPainter()
qp.begin(self)
qp.setRenderHint(QPainter.Antialiasing, True)
qp.setBrush(QColor().fromRgb(2,106,194))
qp.setPen(QColor().fromRgb(2,106,194))
qp.drawRoundRect(QRect(0,0, 300, 300), 16, 8)
qp.end()
def show(self):
self.superShow()
a = QPropertyAnimation(self, "windowOpacity")
a.setDuration(500)
a.setStartValue(1)
a.setEndValue(0)
a.start()
def hide(self):
a = QPropertyAnimation(self, "windowOpacity")
a.setDuration(500)
a.setStartValue(0)
a.setEndValue(1)
a.finished.connect(self.superHide)
a.start()
def superShow(self):
super(widget, self).show()
def superHide(self):
super(widget, self).hide()
No error messages at all it just hides and shows after the animation duration is over. No idea where to look or what to do to get it working. I've only been coding for like 3 months or so.
Your code has many errors, for example:
I don't see where you call init().
Animations are local variables that will be removed when the show and hide methods are finished, which is almost instantaneous.
etc.
Instead of changing the opacity directly I will use QGraphicsOpacityEffect, instead of using the show and close method, I will use the showEvent, hideEvent and closeEvent methods.
import sys
from PySide2.QtCore import QEasingCurve, QEventLoop, QPropertyAnimation, QRect, QSize, Qt
from PySide2.QtGui import QColor, QPainter
from PySide2.QtWidgets import QAction, QApplication, QGraphicsOpacityEffect, QWidget
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(QSize(300, 300))
# self.setWindowOpacity(1)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setContextMenuPolicy(Qt.ActionsContextMenu)
quit_action = QAction(self.tr("E&xit"), self)
quit_action.setShortcut(self.tr("Ctrl+Q"))
quit_action.triggered.connect(self.close)
self.addAction(quit_action)
effect = QGraphicsOpacityEffect(self, opacity=1.0)
self.setGraphicsEffect(effect)
self._animation = QPropertyAnimation(
self,
propertyName=b"opacity",
targetObject=effect,
duration=500,
startValue=0.0,
endValue=1.0,
)
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHint(QPainter.Antialiasing, True)
qp.setBrush(QColor().fromRgb(2, 106, 194))
qp.setPen(QColor().fromRgb(2, 106, 194))
qp.drawRoundedRect(QRect(0, 0, 300, 300), 16, 8)
def fade_in(self):
self._animation.setDirection(QPropertyAnimation.Forward)
self._animation.start()
def fade_out(self):
loop = QEventLoop()
self._animation.finished.connect(loop.quit)
self._animation.setDirection(QPropertyAnimation.Backward)
self._animation.start()
loop.exec_()
def showEvent(self, event):
super().showEvent(event)
self.fade_in()
def closeEvent(self, event):
# fade out
self.fade_out()
super().closeEvent(event)
def hideEvent(self, event):
# fade out
self.fade_out()
super().hideEvent(event)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Maybe it is insufficient knowledge of Qt or of Python, of maybe even both, but I have a problem with Qt5 in Python.
I have a script that draws a line from a point where the mouse pointer is depressed to the point where it is released. That works fine.
Then when I want to draw a second line, I want to keep the first line on the QDialog as well, but because the way paintEvent works that is not possible straight away.
There for I've created a helper class (at first it was in the MyDialog class itself) to store all the points in a list and then use this class to redraw all the lines when paintEvent redraws the QDialog.
However, this is not working because for some reason all the points stored in the helper class get overridden by the last point. So if I draw ten lines, the helper class has 10 times the last (10th) point in its list.
Below you can see the code, can somebody shine a light on this? Thank!
import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtGui import QPainter, QPicture
from demoDrawLine import *
class ContextTest:
test = []
class MyForm(QDialog):
picture = []
def __init__(self):
super().__init__()
self.drawing = []
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.pos1 = [0,0]
self.pos2 = [0,0]
self.show()
def paintEvent(self, event):
super().paintEvent(event)
qp = QPainter()
qp.begin(self)
qp.drawLine(self.pos1[0], self.pos1[1], self.pos2[0], self.pos2[1])
qp.end()
def mousePressEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton:
self.pos1[0], self.pos1[1] = event.pos().x(), event.pos().y()
def mouseReleaseEvent(self, event):
self.pos2[0], self.pos2[1] = event.pos().x(), event.pos().y()
ContextTest.test.append((self.pos1, self.pos2))
self.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyForm()
w.show()
sys.exit(app.exec_())
Here some results I copied from the debugger:
1st line drawn: ContextTest.test : [([150, 335], [452, 618])]
2nd line drawn: ContextTest.test : [([311, 695], [340, 666]), ([311, 695], [340, 666])]
3rd line drawn: ContextTest.test : [([1444, 249], [1043, 712]), ([1444, 249], [1043, 712]), ([1444, 249], [1043, 712])]
etc...
You are saving the points in test but you are not using it to paint, as you realized paintEvent has no notion of the past so you will have to save those points but better than saving points would be to keep instructions that know how to paint, for example it could add other figures without needing to write a lot of code in it.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Instruction:
def paint(self, painter):
raise NotImplementedError()
class LineInstruction(Instruction):
def __init__(self, line):
self._line = line
def paint(self, painter):
painter.drawLine(self._line)
class ContextTest:
instructions = []
class MyForm(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.drawing = []
self.show()
def paintEvent(self, event):
super().paintEvent(event)
qp = QtGui.QPainter(self)
for instruction in ContextTest.instructions:
instruction.paint(qp)
def mousePressEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton:
self.start = event.pos()
def mouseReleaseEvent(self, event):
l = QtCore.QLine(self.start, event.pos())
instruction = LineInstruction(l)
ContextTest.instructions.append(instruction)
self.update()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MyForm()
w.show()
sys.exit(app.exec_())
I have the code below for a interactive label in PyQt4 that can be clicked, right clicked and scrolled. I am converting the code for PyQt5 and lots of things in my code are right now based on this element.
class ExtendedQLabel(QLabel):
def __init(self, parent):
super().__init__(parent)
def mousePressEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
self.emit(SIGNAL('rightClicked()'))
else:
self.emit(SIGNAL('clicked()'))
def wheelEvent(self, ev):
self.emit(SIGNAL('scroll(int)'), ev.delta())
How do I make this PyQt5 compatible?
Ok, after a lot of things I understood what I was doing wrong:
from PyQt5 import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class ExtendedQLabel(QLabel):
def __init(self, parent):
super().__init__(parent)
clicked = pyqtSignal()
rightClicked = pyqtSignal()
def mousePressEvent(self, ev):
if ev.button() == Qt.RightButton:
self.rightClicked.emit()
else:
self.clicked.emit()
if __name__ == '__main__':
app = QApplication([])
eql = ExtendedQLabel()
eql.clicked.connect(lambda: print('clicked'))
eql.rightClicked.connect(lambda: print('rightClicked'))
eql.show()
app.exec_()
In the line with the text clicked = pyqtSignal() and rightClicked = pyqtSignal()what ties those signals to that class that makes the code above work? Well the answer is the correct indentation, correct indentation will nest the variable to the class instead of randomly creating a variable that has no use. It took me a lot of time to perceive this, so thought lefting this here could be useful if someone google this.
from PyQt5 import QtGui,QtCore,QtWidgets
class Clickable_Label(QtWidgets.QLabel):
def __init__(self):
super().__init__()
clicked = QtCore.pyqtSignal() # signal when the text entry is left clicked
def mousePressEvent(self, event):
self.clicked.emit()
QtWidgets.QLabel.mousePressEvent(self, event)
myLabel=Clickable_Label()
myLabel.setText("Clickable_Label")
myLabel.board_rule_download.clicked.connect(lambda: print('clicked'))`enter code here`
I think the title is fairly self explanatory. I'm working to create a small standalone app that requires the user to drag and drop audio files onto buttons to in turn associate the file with a corresponding button on a piece of hardware by using the filepath, etc...
I've followed a ton of drag and drop tutorials for widgets, and my friend has for lists, however I'm beginning to believe that it can't be done for a button? I'm aware that you can drag and drop text onto a button. I am not fully up to speed with Qt yet so there may just be a glaring error that I'm missing.
Here is the code, many thanks!
import sys
from PyQt4 import QtGui, QtCore
class Button(QtGui.QPushButton):
def __init__(self, parent):
super(Button, self).__init__(parent)
self.setAcceptDrops(True)
self.setDragDropMode(QAbstractItemView.InternalMove)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.acceptProposedAction()
else:
super(Button, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
super(Button, self).dragMoveEvent(event)
def dropEvent(self, event):
if event.mimeData().hasUrls():
for url in event.mimeData().urls():
path = self.addItem(url.path())
print path
event.acceptProposedAction()
else:
super(Button,self).dropEvent(event)
class MyWindow(QtGui.QWidget):
def __init__(self):
super(MyWindow,self).__init__()
self.setGeometry(100,100,300,400)
self.setWindowTitle("Filenames")
self.btn = QtGui.QPushButton()
self.btn.setGeometry(QtCore.QRect(90, 90, 61, 51))
self.btn.setText("Change Me!")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.btn)
self.setLayout(layout)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
There are three problems with your posted code, the main being that you aren't even using the custom Button class that you made. You are adding just a regular button to your window with:
self.btn = QtGui.QPushButton()
instead of:
self.btn = Button(self)
Also, QPushButtons don't have a setDragDropMode() method, so you'll need to comment that line out. I'm not sure what it does anyway.
Also, QPushButton doesn't have an addItem() method so I'm not sure what that's about unless you were planning on implementing it. I replaced it below with just printing the file path.
Here is a working version of your code, that just prints the file path of any file dragged into the button:
import sys
from PyQt4 import QtGui, QtCore
class Button(QtGui.QPushButton):
def __init__(self, parent):
super(Button, self).__init__(parent)
self.setAcceptDrops(True)
#self.setDragDropMode(QAbstractItemView.InternalMove)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.acceptProposedAction()
else:
super(Button, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
super(Button, self).dragMoveEvent(event)
def dropEvent(self, event):
if event.mimeData().hasUrls():
for url in event.mimeData().urls():
print str(url.toLocalFile())
event.acceptProposedAction()
else:
super(Button,self).dropEvent(event)
class MyWindow(QtGui.QWidget):
def __init__(self):
super(MyWindow,self).__init__()
self.setGeometry(100,100,300,400)
self.setWindowTitle("Filenames")
self.btn = Button(self)
self.btn.setGeometry(QtCore.QRect(90, 90, 61, 51))
self.btn.setText("Change Me!")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.btn)
self.setLayout(layout)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())