I am very new to using PyQt and am trying to understand the signal slot mechanism. Unfortunately, documentation for PyQt often leads to Qt pages where syntax and parameters are hardly the same. I am trying to figure out 2 things in the simple example below.
1) QAction::triggered() is a void function, so how are we calling QAction::triggered.connect() on some sort of object that would theoretically be returned by the triggered() method.
2) And what is "qApp". I don't know what type qApp is or where it is created by PyQt but it seems to appear out of nowhere to me, only to be used at a convenient time.
Part of my misunderstanding probably comes from the fact that the C++ and python implementation of functions in Qt/PyQt are not the same but we are expected to understand what is going on without any sort of python docs.
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication
from PyQt5.QtGui import QIcon
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
exitAction = QAction(QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(qApp.quit)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Toolbar')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
1/ : The syntax to connect a signal automatically carry the arguments to the dedicated callback
In your case, there is no argument.
I simplify your code to show you the callback mechanism
2/ : the qApp is a kind of shortcut to your Qapplication instance. You could replace it with your QApplication instance as in the following example.
Extract from the QApplication documentation :
The QApplication object is accessible through the instance() function that returns a pointer equivalent to the global qApp pointer.
The global qApp pointer refers to this application object. Only one application object should be created.
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication
class Example(QMainWindow):
def __init__(self):
super(Example, self).__init__()
exitAction = QAction('Exit', self)
exitAction.triggered.connect(self.this_call)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)
self.show()
def this_call(self):
print('bye bye')
app.quit()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Related
I have an RGB888 format qImage defined as follows:
int sizeX = 300; int sizeY = 300;
QImage img = QImage(sizeX, sizeY, QImage::Format_RGB888);
I wish to print current pixel of img one by one. So, I followed the example here:`
for(int i=0; i<sizeX; i++){
for(int j=0; j<sizeY; j++){
img.setPixel(i, j, qRgb(rand()%256, rand()%256, rand()%256));
}
}
QGraphicsScene *graphic = new QGraphicsScene(this);
graphic->addPixmap(QPixmap::fromImage(img));
ui->graphicsView->setScene(graphic);
But it prints the whole QImage. I want to print one by one.
Thanks. Best regards.
As JarMan commented, you are looking for "animation".
Drawing pixel by pixel, and updating the UI for every pixel, may use animation solution as described in the following post.
In Qt5 we have to execute QtWidgets.QApplication.processEvents() for forcing Qt to redraw.
Note:
I implemented the code in Python (with PyQt5), the syntax is different from C++, but the concept is the same.
Initialization stage:
The main class is inherited from QGraphicsView class.
Create QGraphicsScene object:
graphic = QGraphicsScene(0, 0, sizeX, sizeY)
Create QPixmap object from image:
pixmap = QPixmap.fromImage(img)
add addPixmap to the graphic object, and keep the returned reference:
pixmap_item = graphic.addPixmap(pixmap)
Execute setScene(graphic)
Animation:
In the animation loop, we have to execute setPixmap on every iteration for updating the entire image.
(The is a way for drawing pixel by pixel, but it's a deviation from your question).
Simplified code for updating the image (executed each iteration):
img.setPixel(i, j, qRgb(randrange(256), randrange(256), randrange(256)));
pixmap = QPixmap.fromImage(img)
pixmap_item.setPixmap(pixmap)
QtWidgets.QApplication.processEvents()
Complete Python code sample:
import sys
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QApplication
from PyQt5 import QtWidgets
from PyQt5.QtGui import QImage, QPixmap, QPainter, QColor, qRgb
from random import randrange
import time
class Example(QGraphicsView):
def __init__(self):
super().__init__()
self.mPixmap = QPixmap()
self.sizeX = 300
self.sizeY = 300
self.img = QImage(self.sizeX, self.sizeY, QImage.Format_RGB888)
self.img.fill(QColor(60, 60, 60)) # Fill image with gray color
self.initUI()
def initUI(self):
self.graphic = QGraphicsScene(0, 0, self.sizeX, self.sizeY)
self.pixmap = QPixmap.fromImage(self.img)
self.pixmap_item = self.graphic.addPixmap(self.pixmap)
self.setScene(self.graphic)
self.show()
def doAnimation(self):
for i in range(self.sizeX):
for j in range(self.sizeY):
self.img.setPixel(i, j, qRgb(randrange(256), randrange(256), randrange(256)));
pixmap = QPixmap.fromImage(self.img)
self.pixmap_item.setPixmap(pixmap)
QtWidgets.QApplication.processEvents()
time.sleep(0.001) # Sleep 1msec (In Python it's slow enough without the sleep...)
if __name__ == '__main__':
app = QApplication(sys.argv)
example = Example()
example.doAnimation()
app.exec_()
I hope it's not too much Python for you...
Sample output:
I am trying to write a simple application in PyQt5 with one QWidget that has the following two behaviors:
Overrides the mouse cursor
Transparent for input; meaning that it can ignore mouse inputs and send it to the background widget or OS UI (i.e. like an overlaying behavior)
I can achieve each behavior but individually i.e. When I combine them together the cursor returns to it's default state (i.e. I lose the ability to override it)!
I wonder if this is achievable in QT?
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
# Goal no. 1: override mouse cursor
QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
w = QWidget()
w.setWindowOpacity(0.1)
# Goal no.2: Make widget transparent for input
w.setWindowFlags(w.windowFlags()
| QtCore.Qt.WindowTransparentForInput | QtCore.Qt.WindowStaysOnTopHint)
w.resize(900, 900)
w.show()
sys.exit(app.exec_())
Based on the great advice of #ekhumoro:
I found a solution to use a transparent widget and draw an animated shape on the screen that will follow the mouse cursor
The steps are:
Create a fully transparent widget (i.e. completely hidden)
This widget shall cover the whole screen; Similar to screenshot applications
This widget is then configured to ignore all mouse inputs ⇒ It should allow mouse cursor to interact with all UI behind it exactly as if this widget does not exist as all (e.g. user can click through this widget)
Then shapes/texts/animations can be drawn and moved on that transparent widget
The coordinates of these shapes can be determined based on the mouse cursor coordinates
The only limitation of this approach is that we not override the system cursor but we will be shown animations next to it which I believe satisfies the original use case
from PySide6 import QtCore, QtWidgets, QtGui
from PySide6.QtCore import QTimer
from PySide6.QtGui import QPainter, QFont
from PySide6.QtWidgets import QApplication
class TransparentWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TransparentWidget, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# https://stackoverflow.com/questions/52827296/pyside2-pass-mouse-events-to-system
self.setWindowFlags(self.windowFlags()
# | QtCore.Qt.WindowTransparentForInput
| QtCore.Qt.X11BypassWindowManagerHint
| QtCore.Qt.WindowStaysOnTopHint
| QtCore.Qt.WA_MouseNoMask
| QtCore.Qt.WA_TranslucentBackground
| QtCore.Qt.FramelessWindowHint)
self.x = 0
self.y = 0
self.number = 4
def paintEvent(self, event):
print(f"paintEvent {self.x} {self.y}")
if not self.number:
return
painter = QPainter()
painter.begin(self)
font = QFont()
font.setBold(True)
font.setPixelSize(15)
painter.setFont(font)
pen = QtGui.QPen()
pen.setWidth(3)
pen.setColor(QtCore.Qt.red)
painter.setPen(pen)
painter.setBrush(QtCore.Qt.white)
painter.drawEllipse(self.x + 15, self.y + 15, 30, 30)
pen = QtGui.QPen()
pen.setColor(QtCore.Qt.black)
painter.setPen(pen)
painter.drawText(self.x + 26, self.y + 35, str(self.number))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = TransparentWidget()
w.showFullScreen()
def func():
w.x += 1
w.y += 1
w.update()
timer = QTimer()
timer.timeout.connect(func)
timer.start(10)
sys.exit(app.exec())
I have this simple scene showing a QGraphicsPixmapItem. I update its content periodically from a QThread. The first time it is updated, it displays correctly. However the second time around, the image disappears. Any idea why?
import numpy as np
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QGraphicsPixmapItem, QGraphicsScene, QGraphicsView, QMainWindow
import sys
app = QApplication([])
window = QMainWindow()
window.setGeometry(100, 100, 400, 400)
view = QGraphicsView()
scene = QGraphicsScene()
gpix = QGraphicsPixmapItem()
scene.addItem(gpix)
view.setScene(scene)
window.setCentralWidget(view)
window.show()
class Thread(QThread):
def run(self):
while True:
im = np.random.randint(255, size=(256, 256), dtype=np.uint8)
qim = QImage(im, im.shape[1], im.shape[0], QImage.Format_Grayscale8)
pix = QPixmap.fromImage(qim)
gpix.setPixmap(pix)
self.sleep(1)
thread = Thread()
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())
There are two basic issues. Firstly you are potentially accessing the same variable gpix simultaneously from multiple threads. Secondly you are trying to use QPixmap from a secondary thread (a thread other than that on which main is running).
To fix the first problem you can use a signal/slot to notify the main thread that a new image is available.
For the second problem you can defer the creation of the QPixmap from the QImage until the point at which the signal is handled.
import numpy as np
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QGraphicsPixmapItem, QGraphicsScene, QGraphicsView, QMainWindow
import sys
app = QApplication([])
window = QMainWindow()
window.setGeometry(100, 100, 400, 400)
view = QGraphicsView()
scene = QGraphicsScene()
gpix = QGraphicsPixmapItem()
scene.addItem(gpix)
view.setScene(scene)
window.setCentralWidget(view)
window.show()
class Thread(QThread):
# Add a signal that accepts a QImage.
sig_new_image = pyqtSignal(QImage)
def run(self):
while True:
im = np.random.randint(255, size=(256, 256), dtype=np.uint8)
qim = QImage(im, im.shape[1], im.shape[0], QImage.Format_Grayscale8)
pix = QPixmap.fromImage(qim)
# Emit the signal with the new QImage.
self.sig_new_image.emit(qim)
self.sleep(1)
thread = Thread()
thread.finished.connect(app.exit)
# Connect the Thread.sig_new_image signal to a lambda that will update
# the pixmap accordingly.
thread.sig_new_image.connect(lambda i: gpix.setPixmap(QPixmap.fromImage(i)))
thread.start()
sys.exit(app.exec_())
I'm learning Pyside QProgressBar on MacOSX. When I use QProgressBar like following, it only indicate 0% or 100%. How to make a QProgressBar smoothly? Is there any way to do this?
from PySide.QtGui import QApplication, QProgressBar, QWidget
from PySide.QtCore import QTimer
import time
app = QApplication([])
pbar = QProgressBar()
pbar.setMinimum(0)
pbar.setMaximum(100)
pbar.show()
def drawBar():
global pbar
pbar.update()
t = QTimer()
t.timeout.connect(drawBar)
t.start(100)
for i in range(1,101):
time.sleep(0.1)
pbar.setValue(i)
app.exec_()
Get rid of this code:
for i in range(1,101): # this won't work, because
time.sleep(0.1) # Qt's event loop can't run while
pbar.setValue(i) # you are forcing the thread to sleep
and instead, add a global variable p:
p = 0
and increment it in your drawBar() function:
def drawBar():
global pbar
global p
p = p + 1
pbar.setValue(p)
pbar.update()
QPropertyAnimation is easy to use and it does the smooth change for you.
animation = QtCore.QPropertyAnimation(pbar, "value")
animation.setDuration(???)
animation.setStartValue(0)
animation.setEndValue(100)
animation.start()
Edited post:
Just replace everything between pbar.show() and app.exec() by the code I suggested
Here is the complete code:
from PyQt5.QtWidgets import QWidget, QProgressBar, QApplication
from PyQt5.QtCore import QTimer, QPropertyAnimation
app = QApplication([])
pbar = QProgressBar()
pbar.setMinimum(0)
pbar.setMaximum(100)
pbar.show()
animation = QPropertyAnimation(pbar, "value")
animation.setDuration(2000)
animation.setStartValue(0)
animation.setEndValue(100)
animation.start()
app.exec_()
I'm trying to get the boundingRect() of a subclass of a QGraphicsRectItem while it is being moved by the user. However, as the rectangle is moved in the scene, the rect returned by boundingRect() always returns the original value for the bounding rectangle. The example below demonstrates the issue. The value of boundinRect() is printed for every mouse move while the RectItem is moved around the scene. Why isn't boundingRect() returning the new bounding rectangle? What am I doing wrong?
from sys import argv
from PyQt4.Qt import QApplication
from PyQt4.QtCore import QPointF, QRectF, Qt
from PyQt4.QtGui import (
QGraphicsItem,
QGraphicsRectItem,
QGraphicsScene,
QGraphicsView,
)
class RectItem(QGraphicsRectItem):
def __init__(self, parent):
super(RectItem, self).__init__(parent)
self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True)
self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True)
def mouseMoveEvent(self, event):
super(RectItem, self).mouseMoveEvent(event)
print self.boundingRect()
class ViewItem(QGraphicsRectItem):
def __init__(self, x, y, width, height, parent=None, scene=None):
super(ViewItem, self).__init__(parent, scene)
self.setRect(QRectF(x, y, width, height))
self.create_child_item(100, 100)
def create_child_item(self, x, y):
self.child_item = RectItem(self)
self.child_item.setRect(x, y, 200, 100)
self.child_item.setSelected(True)
class View(QGraphicsView):
def __init__(self, parent=None):
super(View, self).__init__(parent)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setInteractive(True)
self.graphics_scene = QGraphicsScene(self)
self.setScene(self.graphics_scene)
self.setAlignment(Qt.AlignTop)
self.add_view_item()
def add_view_item(self):
self.view_item = ViewItem(0.0, 0.0, 1024.0, 768.0)
self.graphics_scene.addItem(self.view_item)
if __name__ == '__main__':
app = QApplication(argv)
view = View()
view.setGeometry(100, 100, 1024, 768)
view.setWindowTitle('View Test')
view.show()
app.exec_()
QGraphicsItem's boundingRect() is always in its own coordinate. Moving a item doesn't change its bounding rect relative to itself.
If you need the bounding box of the item relative to the scene, use sceneBoundingRect()