PySide2 Signal cannot receive QObject parameter - python

In PySide2 I have a custom defined object:
class ResizeEvent(QObject):
def __init__(self, qresizeevent=None):
...
I want to a child widget emit a signal when it is resized and the parent widget receives and handles this event. So I defined a Signal in the child widget, a Slot in the parent widget, and connect them:
class ChildWidget(QWidget):
resized = Signal(ResizeEvent)
class ParentWidget(QWidget):
#Slot(ResizeEvent)
def onResized(self, event):
print("onResized", event)
...
...
def __init__(self):
...
self.child.resized.connect(self.onResized)
So that each time the child widget is resized, the parent widget will receive a signal and print out a string "onResized" followed by the custom event object. But the printed event object is None, and of course the following code goes into error. If I pass some int parameters instead of a ResizeEvent parameter into the signal, I got the correct parameters. So do a QtCore.QResizeEvent parameter.
It seems that my custom defined object cannot be successfully passed through the signal-slot connection. Why is it happening? Have I missed anything?
A simplified reproducible example is presented here:
import sys
from PySide2.QtCore import QObject, Signal, Slot
from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
class ResizeEvent(QObject):
def __init__(self, qresizeevent=None):
self.size = qresizeevent.size()
self.oldSize = qresizeevent.oldSize()
class ChildWidget(QWidget):
resized = Signal(ResizeEvent)
def resizeEvent(self, event):
super().resizeEvent(event)
self.resized.emit(ResizeEvent(event))
class ParentWidget(QWidget):
def __init__(self):
super().__init__()
self.child = ChildWidget()
self.child.setFixedSize(200,50)
self.button = QPushButton("Increase Size")
self.layout = QVBoxLayout()
self.layout.addWidget(self.child)
self.layout.addWidget(self.button)
self.setLayout(self.layout)
self.child.resized.connect(self.onResized)
self.button.clicked.connect(self.onClicked)
#Slot(bool)
def onClicked(self, checked):
self.child.setFixedSize(self.child.width() + 10, self.child.height() + 10)
#Slot(ResizeEvent)
def onResized(self, event):
print("onResized", event)
if __name__ == "__main__":
app = QApplication()
widget = ParentWidget()
widget.show()
sys.exit(app.exec_())

When you inherit you must call the constructor of the class that you inherit:
class ResizeEvent(QObject):
def __init__(self, qresizeevent=None):
super(ResizeEvent, self).__init__() # <----
self.size = qresizeevent.size()
self.oldSize = qresizeevent.oldSize()

Related

PyQt6: How can I fetch position of mouse pointer from QGraphicsScene and use the variables in the other class?

I'm new to pyqt6 and even to python. As the title indicates, I got stuck with handling mouse position variables. I was expecting to show coodinates of mouse pointer in the QLabel correspond to the mouse movement. I was able to fetch coordinates from mouseMoveEvent in the QGraphicsScene class to getPosition in the Window class. But, that coodinates couldn't be passed to the other function in Window class.
Here is my code so far.
import sys
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QVBoxLayout, QWidget, QLabel
class GraphicsScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
def mouseMoveEvent(self, event):
self.posX = event.scenePos().x()
Window.getPosition(Window, event.scenePos().x())
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setMouseTracking(True)
scene = GraphicsScene(self)
self.setScene(scene)
class Window(QWidget):
def __init__(self):
super().__init__()
self.Layout = QVBoxLayout()
self.gw = GraphicsView() # an image is meant to be set here.
self.Layout.addWidget(self.gw)
self.label = QLabel("Coordinate: x") # wanna show coorinates here correspond to the mouse movement.
self.Layout.addWidget(self.label)
self.setLayout(self.Layout)
def getPosition(self, posX):
self.label.setText("Coordinate: x" + str(self.posX))
self.repaint()
print(posX)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec())
and got an ERROR like this:
AttributeError: type object 'Window' has no attribute 'label'
It seemas the self in the getPosition function is set to GraphicsScene class after being called (unexpectedly for me). And I have no idea this approach works or not after reading several web documents and asking help for chatGPT. Possibly, I took wrong approach to layout of Widgets.
Any suggestion would be helpful because I'm stuck whole this week with dipression.
Thanks.
Using class Window as parameter to setPosition was wrong, one needs to use an instance of this class. I did this by climbing up the parent() methods and there may be prettier ways to achieve the same. However, it works for now and I did not want to throw in too many changes.
I marked notable changes with comments.
#!/usr/bin/env python3
import sys
from PyQt6 import QtWidgets
class GraphicsScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
def mouseMoveEvent(self, event):
self.posX = event.scenePos().x()
self.parent().parent().setPosition(event.scenePos().x()) # <-- crawl up the ancestry
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setMouseTracking(True)
scene = GraphicsScene(self)
self.setScene(scene)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.Layout = QtWidgets.QVBoxLayout()
self.gw = GraphicsView(self) # <-- pass self here
self.Layout.addWidget(self.gw)
self.label = QtWidgets.QLabel("Coordinate: x") # wanna show coorinates here correspond to the mouse movement.
self.Layout.addWidget(self.label)
self.setLayout(self.Layout)
def setPosition(self, posX): # <-- this is a setter, not a getter
self.label.setText("Coordinate: x" + str(posX)) # <-- use argument posX
self.repaint()
print(posX)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec())

PyQt5 - Detecting When Other Window Closes

I’m using PyQt5 and I need to have my main window detect when a different window closes. I read here Emit a signal from another class to main class that creating a signal class to serve as an intermediary should work. However I haven’t gotten my example to work.
In my example, clicking the button opens a QWidget window. When the QWidget is closed, the main window is supposed to change from a blue background to a red background. However, the main window remains blue using the script below.
What am I doing wrong?
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtCore import QObject, pyqtSignal
import os, sys
class MySignal(QObject):
signal = pyqtSignal()
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
#Make mainwindow
self.setGeometry(100,100,300,200)
self.setStyleSheet('background-color: blue')
# Make widget and button objects and set connection
self.widget = MyWidget()
self.btn = QPushButton(self)
self.btn.setText('Click')
self.btn.move(175, 150)
self.btn.setStyleSheet('background-color: white')
self.btn.clicked.connect(self.widget.showWidget)
# Make signal object and set connection
self.mySignal = MySignal()
self.mySignal.signal.connect(self.changeToRed)
# Let's start
self.show()
def changeToRed(self):
self.setStyleSheet('background-color: red')
def closeEvent(self, event):
os._exit(0)
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(500, 100, 200, 200)
self.sig = MySignal()
def showWidget(self):
self.show()
def closeEvent(self, event):
self.sig.signal.emit()
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyMainWindow()
app.exec()```
The reason your code fails is that the connected signal object in MyMainWindow is not the same as the one you create in MyWidget, so the signal is never emitted. Here is a modified solution using signals in the normal way:
from PyQt5.QtWidgets import QPushButton, QMainWindow, QWidget, QApplication
from PyQt5.QtCore import QObject, pyqtSignal
import os, sys
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
#Make mainwindow
self.setGeometry(100,100,300,200)
self.setStyleSheet('background-color: blue')
# Make widget and button objects and set connection
self.widget = MyWidget()
self.btn = QPushButton(self)
self.btn.setText('Click')
self.btn.move(175, 150)
self.btn.setStyleSheet('background-color: white')
self.btn.clicked.connect(self.widget.showWidget)
self.widget.sig.connect(self.changeToRed)
# Let's start
self.show()
def changeToRed(self):
self.setStyleSheet('background-color: red')
class MyWidget(QWidget):
sig = pyqtSignal()
def __init__(self):
super().__init__()
self.setGeometry(500, 100, 200, 200)
def showWidget(self):
self.show()
def closeEvent(self, event):
self.sig.emit()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyMainWindow()
app.exec()
All you need is to add the connection between the close event and the function that turn your main screen red:
self.widget.closeEvent = self.changeToRed
this line should be in your main Window class
change your changeToRed function so it will except the event too:
def changeToRed(self, e):

How to emit custom Events to the Event Loop in PyQt

I am trying to emit custom events in PyQt. One widget would emit and another would listen to events, but the two widgets would not need to be related.
In JavaScript, I would achieve this by doing
// Component 1
document.addEventListener('Hello', () => console.log('Got it'))
// Component 2
document.dispatchEvent(new Event("Hello"))
Edit: I know about signals and slots, but only know how to use them between parent and child. How would I this mechanism (or other mechanism) between arbitrary unrelated widgets?
In PyQt the following instruction:
document.addEventListener('Hello', () => console.log('Got it'))
is equivalent
document.hello_signal.connect(lambda: print('Got it'))
In a similar way:
document.dispatchEvent(new Event("Hello"))
is equivalent
document.hello_signal.emit()
But the big difference is the scope of the "document" object, since the connection is between a global element. But in PyQt that element does not exist.
One way to emulate the behavior that you point out is by creating a global object:
globalobject.py
from PyQt5 import QtCore
import functools
#functools.lru_cache()
class GlobalObject(QtCore.QObject):
def __init__(self):
super().__init__()
self._events = {}
def addEventListener(self, name, func):
if name not in self._events:
self._events[name] = [func]
else:
self._events[name].append(func)
def dispatchEvent(self, name):
functions = self._events.get(name, [])
for func in functions:
QtCore.QTimer.singleShot(0, func)
main.py
from PyQt5 import QtCore, QtWidgets
from globalobject import GlobalObject
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
button = QtWidgets.QPushButton(text="Press me", clicked=self.on_clicked)
self.setCentralWidget(button)
#QtCore.pyqtSlot()
def on_clicked(self):
GlobalObject().dispatchEvent("hello")
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
GlobalObject().addEventListener("hello", self.foo)
self._label = QtWidgets.QLabel()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._label)
#QtCore.pyqtSlot()
def foo(self):
self._label.setText("foo")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w1 = MainWindow()
w2 = Widget()
w1.show()
w2.show()
sys.exit(app.exec_())

How to insert a Qwidget inside Qwidget

I am lost with all the parenting/initialising issues and have no idea why this does not work.
So I create a Label, then I create another Label with some painting in it, them I make a widget that contains the two, then I would like to put this new widget inside the main window... but nothing appears
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Labhtml(QLabel):
def __init__(self):
super().__init__()
label = QLabel('html')
class Bar(QLabel):
def __init__(self):
super().__init__()
self.resize(100, 5)
def paintEvent(self, e):
qp = QPainter(self)
qp.setBrush(QColor(200, 0, 0))
qp.drawRect(0,0,200,3)
class Wid(QWidget):
def __init__(self, parent):
super().__init__(parent=parent)
widget = QWidget()
html = Labhtml()
bar = Bar()
self.layout = QVBoxLayout(widget)
self.layout.addWidget(html)
self.layout.addWidget(bar)
class Example(QScrollArea):
def __init__(self):
super().__init__()
widget = QWidget()
layout = QVBoxLayout(widget)
layout.addWidget(Wid(widget))
self.setWidget(widget)
self.setWidgetResizable(True)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
First for the class Labhtml, when you inherit from QLabel, you can use the methods and the attributes of the base class, or use the instantiation mechanism to pass some parameters :
class Labhtml(QLabel):
def __init__(self):
super().__init__()
self.setText('html')
Then you don't need to create another widget inside the Wid class, but you have to refer to self instead :
class Wid(QWidget):
def __init__(self, parent):
super().__init__(parent=parent)
html = Labhtml()
bar = Bar()
self.layout = QVBoxLayout(self)
self.layout.addWidget(html)
self.layout.addWidget(bar)
About the instantiation mechanism you could also write the classes by declaring a new text argument (the same used for the Qlabel), and pass it when you create your instance :
class Labhtml(QLabel):
def __init__(self, text):
super().__init__(text)
class Wid(QWidget):
def __init__(self, parent):
super().__init__(parent=parent)
html = Labhtml('html')

Send and receive signals from another class pyqt

I am needing a way to receive signals sent by a Class to another class.
I have 2 classes:
In my first class I have a function that emits a signal called 'asignal'
In my second class I call the first class function, and it emits a signal. but I can't connect the first class signal to my pushbutton. How I can do that?
I get this error: AttributeError: 'QPushButton' object has no attribute 'asignal'
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import *
import sys
class Signals(QObject):
asignal = pyqtSignal(str)
def __init__(self):
super(Signals, self).__init__()
self.do_something()
def do_something(self):
self.asignal.emit('Hi, im a signal')
class Main(QWidget):
def __init__(self):
super(Main, self).__init__()
self.setGeometry(300, 250, 400, 300)
self.show()
self.coso()
def coso(self):
btn = QPushButton('click me')
btn.asignal.connect(lambda sig: print("Signal recieved" + sig))
s = Signals()
s.do_something()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
app.exec_()
I want a way to receive the signal emitted from my 'Signals' class to my 'Main' class. And then, in my main class, connect the signal to a Widget.
QPushButton is not a Signals object, so thats why you get the error.
s = Signals()
s.asignal.connect(lambda sig: print("Signal recieved" + sig))
s.do_something()

Categories

Resources