I try hard to understand how to identify an instance of a class within drag and drop, but i need some help.
Beneath I add a sample from "http://zetcode.com/gui/pyqt5/dragdrop/" to explain.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
ZetCode PyQt5 tutorial
In this program, we can press on a button with a left mouse
click or drag and drop the button with the right mouse click.
Author: Jan Bodnar
Website: zetcode.com
Last edited: August 2017
"""
from PyQt5.QtWidgets import QPushButton, QWidget, QApplication
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag
import sys
class Button(QPushButton):
def __init__(self, title, parent):
super().__init__(title, parent)
def mouseMoveEvent(self, e):
if e.buttons() != Qt.RightButton:
return
mimeData = QMimeData()
drag = QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(e.pos() - self.rect().topLeft())
dropAction = drag.exec_(Qt.MoveAction)
def mousePressEvent(self, e):
super().mousePressEvent(e)
if e.button() == Qt.LeftButton:
print('press')
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setAcceptDrops(True)
self.button = Button('Button', self)
self.button.move(100, 65)
self.setWindowTitle('Click or Move')
self.setGeometry(300, 300, 280, 150)
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
position = e.pos()
self.button.move(position)
e.setDropAction(Qt.MoveAction)
e.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()
Here my questions:
if I instantiate a 2nd button
self.button2 = Button('Button2', self)
self.button2.move(100, 165)
i can also move the button2, but in the DropEvent the first button "button" is moved.
How is it possible to identify within DropEvent, that I move "button2" und place button2?
So: question is: how to identify, which button it is.
(i found only drag n drop examples, how to replace a label or a button by another element of same type)
And additional question: to create a new instance works with this type of code. If I create the buttons within QT5 designer, it is not possible to have them as an instance of this type of class Button in this code.
So, defining drag n drop in the QT5 designer, drag n drop of an item within a list to another list works, but i am not able to identify, which element I have dragged
If the object you are dragging from is a QWidget then you can get it through the QDropEvent source() method, and the QPushButton is a QWidget so it works in this case:
def dropEvent(self, e):
if e.source() is not None:
position = e.pos()
e.source().move(position)
e.setDropAction(Qt.MoveAction)
e.accept()
Remember to verify that it is not None since the source may be another window that is not a QWidget so you would get an exception
If you want the buttons added through Qt Designer you want them to have the same functionality then the easiest solution is to promote the buttons, there are many examples in SO where they show how to do it:
Clear QLineEdit on click event
How to insert video in ui file which made at qt designer?
Painting in a QLabel with paintEvent, etc
You are always using self.button in your drop event, so it refers to the same button. Instead you can use QDropEvent.source() to get the correct widget.
def dropEvent(self, e):
position = e.pos()
e.source().move(position) # source widget
e.setDropAction(Qt.MoveAction)
e.accept()
Related
I created a small GUI that allows me to draw a number. That number is supposed to be classified with a CNN. The CNN is not connected to this GUI yet. Will do that later on. I am still very new to PyQt5 and used some code that i found online for the drawing with QImage. It works, but at the moment i can draw all over the GUI. Is it possible to place that in a widget? So that I can only draw inside a specific frame and not all over the GUI?
So basically how can i get the self.image = QImage(...) iside a widget or something that i created earlier on my GUI? Is that possible somehow or would you even suggest to solve it in totaly different way?
import sys
from PyQt5 import QtWidgets
from PyQt5.QtGui import QIcon, QImage, QPainter, QPen, QBrush
from PyQt5.QtCore import Qt, QPoint
from UI.mainwindow_2 import Ui_MainWindow
import matplotlib.pyplot as plt
import numpy as np
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.image = QImage(self.size(), QImage.Format_RGB32)
self.image.fill(Qt.white)
self.drawing = False
self.brushSize = 28
self.brushColor = Qt.black
self.lastPoint = QPoint()
self.ui.Clear.clicked.connect(self.clear)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = True
self.lastPoint = event.pos()
def mouseMoveEvent(self, event):
if(event.buttons() & Qt.LeftButton) & self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor, self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(self.lastPoint, event.pos())
self.lastPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = False
def paintEvent(self, event):
canvasPainter = QPainter(self)
canvasPainter.drawImage(self.rect(),self.image, self.image.rect())
def clear(self):
self.image.fill(Qt.white)
self.update()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
First of all, the following aspects must be considered:
paint events are received by any (visible) QWidget subclass
painting is always constricted to the geometry of the widget
painting always happens from the bottom to the top, so whenever a widget has some children, whatever has been painted on that parent widget will be potentially covered by those children
So, the first thing to do is to implement the paintEvent only on the actual widget for which you want to paint. Since you're using Designer, this makes things a bit more complex, as there is no way to subclass a widget that already exists in an ui.
Luckily, Qt has a concept called "promoted widgets": it allows to "expand" a certain widget by specifying the custom subclass that will be actually used when the ui will be finally generated in the program.
choose which widget in your ui will be used for painting; I suggest you to start from a basic QWidget, I'll explain more about this later;
right click on it, and select "Promote to..." from the context menu;
in the "Promoted class name" type the exact class name you are going to use (let's say "Canvas");
in the "Header file" field, type the file name that will contain that class, as it would appear in an import statement (meaning that it should not have the py extension!); assuming you want to do everything in a single script and your script is named "mycanvas.py", type "mycanvas" in that field;
ensure that the "Base class name" combo is set to the exact class type of the widget you've chosen (QWidget, in this case, which is usually automatically selected)
click "Add" and then "Promote";
save the ui and generate the file with pyuic;
Now, the implementation of your mycanvas.py file is simple:
class Canvas(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.image = QImage(self.size(), QImage.Format_RGB32)
self.image.fill(Qt.white)
self.drawing = False
self.brushSize = 28
self.brushColor = Qt.black
self.lastPoint = QPoint()
# ... all the painting related methods, as you did in your code
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui.Clear.clicked.connect(self.ui.canvas.clear)
def main():
# ...
Two considerations:
a QFrame usually has some borders, and since painting can always happen within the whole widget size, you might end up painting on the borders too. If you want a QFrame and paint inside that widget, then add a QWidget as a child to that frame and ensure that a layout is set for the frame;
setting the QImage size within the init is not a good approach, as the widget might change its size or it could be initialized with a size smaller than it will eventually have; you either set a fixed size in the __init__ before creating the QImage, or you keep track of the points/lines in an internal container and continuously draw the contents in the paintEvent;
I am trying to create an application using pyqt python.Application's Main window is filled with many dock widgets, some dock widgets are just used to list certain string data. These widgets are occupying more space.
the drawer to the left in the image is my interest. That drawer opens on mouse click.
Is there any way I could hide these widgets to the side of main window and open when mouse is hovered over it?
or if you know any pyqt UI element which could do this. please suggest.
The logic is to detect the desired event and show the widget, in the following example the click on the QGraphicsView is detected and then the QDockWidget that was initially hidden is shown.
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.dock_widget = QtWidgets.QDockWidget()
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock_widget)
list_widgets = QtWidgets.QListWidget()
list_widgets.addItems(["item{}".format(i) for i in range(100)])
self.dock_widget.setWidget(list_widgets)
self.scene = QtWidgets.QGraphicsScene(self)
self.view = QtWidgets.QGraphicsView(self.scene)
it = self.scene.addRect(QtCore.QRectF(0, 0, 300, 400))
it.setBrush(QtGui.QColor("white"))
self.view.viewport().installEventFilter(self)
self.setCentralWidget(self.view)
self.dock_widget.hide()
self.resize(640, 480)
for i in range(4):
self.menuBar().addAction("Action{}".format(i))
def eventFilter(self, obj, event):
if obj is self.view.viewport():
if event.type() == QtCore.QEvent.MouseButtonPress:
self.dock_widget.show()
return super().eventFilter(obj, event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
does anyone know how I can implement the dragMove event on my QWidget? So basically what I want is to move my mouse over the Widget hold down my mouse button and drag it. While dragging, the widget should not be moved it should only capture the mouse coordinates while the mouse is pressed.
I have already googled and just find some drag and drop tutorials where they have dragged something into a widget etc. like text. This wasn't really helpful.
This has got nothing to do with dragging. What you actually need to do is enable mouse-tracking and then monitor mouse-move events.
Here's a simple demo:
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setMouseTracking(True)
def mouseMoveEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton:
print(event.globalPos().x(), event.globalPos().y())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 150, 100, 100)
window.show()
sys.exit(app.exec_())
I think you are looking for mousePressEvent rather than dragMoveEvent. You would need to subclass QWidget and implement the mousePressEvent method providing your implementation:
from PyQt5.QtWidgets import QWidget
class MyWidget(QWidget):
def mousePressEvent(self, event):
print(event.pos())
I'm trying to capture the cursor coordinates as the mouse is moved within a QWidget by reimplementing QWidget::mouseMoveEvent(). With mouse tracking enabled, mouse move events are generated as I move the cursor around the main widget. However, when the cursor is placed over a child widget the mouse move events cease to fire.
Mouse press/release events work while the cursor is over the same child widget, and move events are firing correctly if the mouse button is held. I've tried enabling mouse tracking on the children too, but it doesn't seem to make a difference. How can I trigger mouse move events when the mouse is over a child widget?
Here's a minimum working example that demonstrates the problem:
import sys
from PyQt4 import QtCore, QtGui
class MyWindow(QtGui.QWidget) :
def __init__(self):
QtGui.QWidget.__init__(self)
tabs = QtGui.QTabWidget()
tab1 = QtGui.QWidget()
tab2 = QtGui.QWidget()
tabs.addTab(tab1, "Tab 1")
tabs.addTab(tab2, "Tab 2")
layout = QtGui.QVBoxLayout()
layout.addWidget(tabs)
self.setLayout(layout)
self.setMouseTracking(True)
def mouseMoveEvent(self, event):
print 'mouseMoveEvent: x=%d, y=%d' % (event.x(), event.y())
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.setFixedSize(640, 480)
window.show()
sys.exit(app.exec_())
When the mouse is moved outside of the QTabWidget the mouse coordinates are printed as expected. Inside of it nothing happens unless the mouse button is held.
The problem with your code is that you need to enable mouse tracking for all widgets explicitly. You can do this by iterating over all children of your main widget, and calling setMouseTracking(True) for each of them. Here I've overridden setMouseTracking() to do just that:
import sys
from PyQt4 import QtCore, QtGui
class MyWindow(QtGui.QWidget) :
def __init__(self):
QtGui.QWidget.__init__(self)
tabs = QtGui.QTabWidget()
tab1 = QtGui.QWidget()
tab2 = QtGui.QWidget()
tabs.addTab(tab1, "Tab 1")
tabs.addTab(tab2, "Tab 2")
layout = QtGui.QVBoxLayout()
layout.addWidget(tabs)
self.setLayout(layout)
self.setMouseTracking(True)
def setMouseTracking(self, flag):
def recursive_set(parent):
for child in parent.findChildren(QtCore.QObject):
try:
child.setMouseTracking(flag)
except:
pass
recursive_set(child)
QtGui.QWidget.setMouseTracking(self, flag)
recursive_set(self)
def mouseMoveEvent(self, event):
print 'mouseMoveEvent: x=%d, y=%d' % (event.x(), event.y())
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.setFixedSize(640, 480)
window.show()
sys.exit(app.exec_())
LAST UPDATED 19 / 8 / 2014 14 : 37 Fixed tab bar isn't track mouse move event. (your can see in my code)
I also suggest implemented QWidget.mouseMoveEvent (self, QMouseEvent) as your do. But not only root widget only because it track area of interesting widget, so your have to set mouse move event all widget can track your in your application. So, create delegate method to connect them all and if your have any signal form mouse move event, get current point of mouse it. like this;
import sys
from PyQt4 import QtGui
class QCustomWidget (QtGui.QWidget):
def __init__ (self, parent = None):
super(QCustomWidget, self).__init__(parent)
self.myQTabWidget = QtGui.QTabWidget(self)
self.my1QWidget = QtGui.QWidget()
self.my2QWidget = QtGui.QWidget()
self.myQTabWidget.addTab(self.my1QWidget, 'Tab 1')
self.myQTabWidget.addTab(self.my2QWidget, 'Tab 2')
myQLayout = QtGui.QVBoxLayout()
myQLayout.addWidget(self.myQTabWidget)
self.setLayout(myQLayout)
self.setMouseMoveEventDelegate(self)
self.setMouseMoveEventDelegate(self.myQTabWidget)
self.setMouseMoveEventDelegate(self.myQTabWidget.tabBar())
self.setMouseMoveEventDelegate(self.my1QWidget)
self.setMouseMoveEventDelegate(self.my2QWidget)
def setMouseMoveEventDelegate (self, setQWidget):
def subWidgetMouseMoveEvent (eventQMouseEvent):
currentQPoint = self.mapFromGlobal(QtGui.QCursor.pos())
print currentQPoint.x(), currentQPoint.y()
QtGui.QWidget.mouseMoveEvent(setQWidget, eventQMouseEvent)
setQWidget.setMouseTracking(True)
setQWidget.mouseMoveEvent = subWidgetMouseMoveEvent
appQApplication = QtGui.QApplication(sys.argv)
windowQCustomWidget = QCustomWidget()
windowQCustomWidget.setFixedSize(640, 480)
windowQCustomWidget.show()
sys.exit(appQApplication.exec_())
Regards,
I had the same issue and found the answer here:
self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
I would try making your QTabWidget a logical child of MyWindow by passing self when calling the QTabWidget constructor. Also pass a parent for the children of the tab widgets but pass the tab widget variable tabs to their respective constructors. Without the child hierarchy declared like this, the events might not be forwarded properly to the containing widget as its "children" will be seen as just separate widgets drawn on top of your class from the perspective of the qt scene graph / event queue.
I am new to Qt/PySide. I want QLineEdit to select all text in it when it gets focus. After getting focus and selecting all text, it should select all text only after focus is lost and gained again. It should not select all text when I change cursor position after QLineEdit gains focus. How do I do that?
Update: My current code improved as suggested by Ashwani Kumar. I still can't get it to work though:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class MyLineEdit(QLineEdit):
def __init__(self, parent=None):
super(MyLineEdit, self).__init__(parent)
def focusInEvent(self, e):
self.selectAll()
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(MyLineEdit())
layout.addWidget(MyLineEdit())
top.setLayout(layout)
top.show()
app.exec_()
With focusInEvent, when you click the widget, it gets executed, but since you click, it removes the selected text.
To overcome this, we must use the mousePressEvent, this can be done two ways:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class MyLineEdit(QLineEdit):
def __init__(self, parent=None):
super(MyLineEdit, self).__init__(parent)
def mousePressEvent(self, e):
self.selectAll()
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(MyLineEdit())
layout.addWidget(MyLineEdit())
top.setLayout(layout)
top.show()
app.exec_()
Or you can do it by simply overriding the base QLineEdit class:
txt_demo = QtGui.QLineEdit()
txt_demo.mousePressEvent = lambda _ : txt_demo.selectAll()
However, since we are modifying the mousePressEvent, whenever you try to click text, it will always select all first.
For future visitors, I am posting code that is working for me. As I am a newbie I am not sure if the code contains any malpractices. If it does feel free to comment and I'll update my code/answer. Code:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class LineEdit(QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
self.readyToEdit = True
def mousePressEvent(self, e, Parent=None):
super(LineEdit, self).mousePressEvent(e) #required to deselect on 2e click
if self.readyToEdit:
self.selectAll()
self.readyToEdit = False
def focusOutEvent(self, e):
super(LineEdit, self).focusOutEvent(e) #required to remove cursor on focusOut
self.deselect()
self.readyToEdit = True
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(LineEdit())
layout.addWidget(LineEdit())
top.setLayout(layout)
top.show()
app.exec_()
You have to subclass the QLineEdit and then use the new class instead of QLineEdit.
e.g: -
class MyLineEdit(QtGui.QLineEdit):
def __init__(self, parent=None)
super(MyLineEdit, self).__init__(parent)
def focusInEvent(self, e):
self.selectAll()
lineedit = MyLineEdit()
QTimer solution as seen on QtCentre:
import types
from PyQt4 import QtCore
def bind(func, to):
"Bind function to instance, unbind if needed"
return types.MethodType(func.__func__ if hasattr(func, "__self__") else func, to)
...
self.txtSrc.focusInEvent = bind(lambda w, e: QtCore.QTimer.singleShot(0, w.selectAll), self.txtSrc)
Also the provided solution doesn't require to subclass QLineEdit.
These answers don't really provide the sort of standard ergonomics you'd probably want (and users might expect). For example, if you single-click once on a QLE which is not currently all-selected, and then single-click again, typically you'd want the first click to select-all, and the second click to allow you to place the cursor in the specific spot you have chosen.
This can be achieved simply by doing this:
def mousePressEvent(self, event):
already_select_all = self.text() == self.selectedText()
super().mousePressEvent(event)
if not already_select_all:
self.selectAll()
The question in fact asks about gaining focus, not specifically by mouse-clicking, and indeed, if you are a keyboardist or generally musophobic you'll probably also want the whole text to be selected any time the QLE gains focus, e.g. by tabbing or by use of a QLabel "buddy" mnemonic. This seems to do the job:
class MyLineEdit(QtWidgets.QLineEdit):
def __init__(self, *args):
super().__init__(*args)
self.focus_in_reason = None
def focusInEvent(self, event):
super().focusInEvent(event)
self.selectAll()
self.focus_in_reason = event.reason()
def mousePressEvent(self, event):
super().mousePressEvent(event)
if self.focus_in_reason == QtCore.Qt.MouseFocusReason:
self.selectAll()
self.focus_in_reason = None