I am using Python 3.4 and Qt 5 for the first time. It's easy and I can understand most of functionality which I need. But (there is always "but") I don't understand how to use focusOut/clearFocus/focusIn events.
Am I right that old way:
QObject.connect(self.someWidget, QtCore.SIGNAL('focusOutEvent()'), self.myProcedure)
...does not work in Qt5?
I tried to understand this unsuccessfully. I'll be very thankful for a short example how to catch an event when e.g some of many QLineEdit has lost focus.
The issue here is that focusInEvent/clearFocus/focusOutEvent are not signals, they are event handlers. See for example here. If you want to catch these events you will need to re-implement the event handler on your object, for example by subclassing QLineEdit.
class MyQLineEdit(QLineEdit):
def focusInEvent(self, e):
# Do something with the event here
super(MyQLineEdit, self).focusInEvent(e) # Do the default action on the parent class QLineEdit
In PyQt5 the syntax for signals themselves is simpler. Taking for example the textEdited signal from QLineEdit, you can use that as follows:
self.someWidget.textEdited.connect( self.myProcedure )
This will connect the textEdited signal to your self.myProcedure method. The target method will need to accept the signal outputs, for example:
void textEdited ( const QString & text )
So you could define your self.myProcedure in your class as follows and it will receive the QString sent by that signal.
def myProcedure(self, t):
# Do something with the QString (text) object here
You can also define custom signals as follows:
from PyQt5.QtCore import QObject, pyqtSignal
class Foo(QObject):
an_event = pyqtSignal()
a_number = pyqtSignal(int)
In each of these cases pyqtSignal is used to define a property of the Foo class which you can connect to like any other signal. So for example to handle the above we could create:
def an_event_handler(self):
# We receive nothing here
def a_number_handler(self, i):
# We receive the int
You could then connect() and emit() the signals as follows:
self.an_event.connect( self.an_event_handler )
self.a_number.connect( self.a_number_handler )
self.an_event.emit() # Send the signal and nothing else.
self.a_number.emit(1) # Send the signal wih an int.
The link you posted gives more information on custom signals, signal naming and overloading with the new syntax.
It took me a while to get this figured out so I thought I would post it here to help anyone else having this problem. It is in Python 3.9.6 and PyQt 6.1.2.
This changes the color of the widget when it is in focus and again when it is out of focus.
import sys
from PyQt6 import QtWidgets
class Main(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Qt Testing")
self.setGeometry(0, 0, 640, 120)
h = QtWidgets.QHBoxLayout()
w = ColorQLineEdit("one")
h.addWidget(w)
w = ColorQLineEdit("two")
h.addWidget(w)
self.setLayout(h)
class ColorQLineEdit(QtWidgets.QLineEdit):
def focusInEvent(self, event):
print("in")
self.setStyleSheet("background-color: yellow; color: red;")
super().focusInEvent(event)
def focusOutEvent(self, event):
print("out")
self.setStyleSheet("background-color: white; color: black;")
super().focusOutEvent(event)
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec())
Related
I was trying to analyse the sample code cited here: PyQt - QMessageBox
Here's the snippet:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Window(QMainWindow):
def __init__(self):
super().__init__()
w = QWidget()
b = QPushButton(self)
b.setText("Show message!")
b.clicked.connect(self.showdialog)
w.setWindowTitle("PyQt Dialog demo")
def showdialog(self):
msg = QMessageBox()
msg.setIcon(QMessageBox.Question)
# self.connect(msg, SIGNAL('clicked()'), self.msgbtn)
msg.buttonClicked.connect(self.msgbtn)
msg.exec_()
def msgbtn(self, i):
print("Button pressed is:", i.text())
if __name__ == '__main__':
app = QApplication([])
w = Window()
w.show()
app.exec_()
There are two ways of connecting signals to slots in PyQt. For buttons, it's:
QtCore.QObject.connect(button, QtCore.SIGNAL(“clicked()”), slot_function)
or
widget.clicked.connect(slot_function)
Using it the second way works fine: the msgbtn slot method is called as intended. However, if I try to change it to the more usual, 'PyQt-onic' way of connecting (i.e. the first one - I commented it out in the snippet), the slot method is never called. Could anyone please help me out with this?
the signal you pass to SIGNAL is incorrect, the QMessageBox does not have the clicked signal but the signal is buttonClicked (QAbstractButton *) so the correct thing is:
self.connect(msg, SIGNAL("buttonClicked(QAbstractButton *)"), self.msgbtn)
On the other hand that is not the PyQt-onic style, but the old style which is not recommended to use, but we recommend using the new style.
Old style:
self.connect(msg, SIGNAL("buttonClicked(QAbstractButton *)"), self.msgbtn)
New style:
msg.buttonClicked.connect(self.msgbtn)
For more detail read the docs.
I know this question has been asked in similar ways, but I'm getting lost in the vast world of the many signals I can choose from when using a QSpinBox (or QDoubleSpinBox). I want to connect my function to the editingFinished signal (fine, it works perfectly), but this will not also connect to the arrow-buttons - so I need signals for those as well. I don't want to call my function every time valueChanged is emitted - only when editing is finished, or when the arrows are used.
One way to do this is to reimplement the stepBy method and emit a custom signal. The main advantage of this approach is that it will handle changes made using the up/down keys, as well as the arrow buttons. Here is a basic demo:
import sys
from PyQt4 import QtCore, QtGui
class SpinBox(QtGui.QSpinBox):
stepChanged = QtCore.pyqtSignal()
def stepBy(self, step):
value = self.value()
super(SpinBox, self).stepBy(step)
if self.value() != value:
self.stepChanged.emit()
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.spin = SpinBox()
self.spin.editingFinished.connect(self.handleSpinChanged)
self.spin.stepChanged.connect(self.handleSpinChanged)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.spin)
def handleSpinChanged(self):
print(self.spin.value())
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 150, 50)
window.show()
sys.exit(app.exec_())
This is most likely a duplicate question, but I have to ask it because other answers aren't helping in my case, since I am new to pyqt (switched from tkinter few days ago).
I am wondering if is it possible to connect to an event of a widget like this:
self.lineEdit = QtGui.QLineEdit(self.frame)
self.lineEdit.keyReleaseEvent(lambda: someFunction(QtCore.Qt.Key_A ))
self.lineEdit.setObjectName(_fromUtf8("lineEdit"))
self.horizontalLayout.addWidget(self.lineEdit)
and then...
def someFunction(event):
print(event)
...
My question is how to bind to a specific event from another widget, and connect that event with a function - like btn.clicked.connect(function_goes_here).
In tkinter it's something be like this:
self.Entry.bind("<KeyRelease-a>", lambda event: someFunction(event))
There are a number of different ways to achieve this. A generic way to listen to all events for a given widget, is to install an event-filter on it. All protected functions have a corresponding event type that can be accessed in this way:
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = QLineEdit(self.frame)
self.lineEdit.installEventFilter(self)
def eventFilter(self, source, event):
if source is self.lineEdit:
if event.type() == QEvent.KeyRelease:
print('key release:', event.key())
# the following line will eat the key event
# return True
return super(MainmWindow, self).eventFilter(source, event)
Alternatively, you can sub-class the widget, re-implement the relevant event handler, and emit a custom signal:
class LineEdit(QLineEdit):
keyReleased = pyqtSignal(int)
def keyReleaseEvent(self, event):
self.keyReleased.emit(event.key())
super(LineEdit, self).keyReleaseEvent(event)
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = LineEdit(self.frame)
self.lineEdit.keyReleased.connect(self.handleKeyRelease)
def handleKeyRelease(self, key):
print('key release:' key)
A more hackish variation on this is to overwrite the method directly:
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = QLineEdit(self.frame)
self.lineEdit.keyReleaseEvent = self.handleKeyRelease
def handleKeyRelease(self, event):
print('key release:', event.key())
QLineEdit.keyReleaseEvent(self.lineEdit, event)
Note that if you don't want to invoke the default event handling, you can omit the call to the base-class method.
I don't have enough reputation to reply to #ekhumoro, but wanted to add to their answer with an applied answer specific to MouseEvents for those who might benefit, based on a similar issue I was having.
Scenario
Display an image in the central widget, and connect a custom signal to capture mouse double click actions in the central widget.
Source for test image: https://en.wikipedia.org/wiki/Standard_test_image#/media/File:SIPI_Jelly_Beans_4.1.07.tiff
Consider the following:
A basic QMainWindow class which loads a supplied image_file and shows the image in a custom sub-class of the QLabel class, fit to the window:
import sys
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QImage, QPalette, QMouseEvent
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QScrollArea)
class ImageView(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
self.image = QImage()
def initializeUI(self):
self.setMinimumSize(300, 200)
self.setWindowTitle("Image Viewer")
# self.showMaximized()
self.createMainQLabel()
self.show()
def createMainQLabel(self):
"""Create an instance of the imageQLabel class and set it
as the main window's central widget."""
self.image_qlabel = imageQLabel(self)
self.image_qlabel.resize(self.image_qlabel.pixmap().size())
self.scroll_area = QScrollArea()
self.scroll_area.setBackgroundRole(QPalette.Dark)
self.scroll_area.setAlignment(Qt.AlignCenter)
self.scroll_area.setWidget(self.image_qlabel)
self.setCentralWidget(self.scroll_area)
class imageQLabel(QLabel):
"""Subclass of QLabel for displaying image"""
def __init__(self, parent, image_file="images/SIPI_Jelly_Beans_4.1.07.tiff"):
super().__init__(parent)
self.openImage(image_file)
self.setScaledContents(True)
self.setAlignment(Qt.AlignCenter)
def openImage(self, image_file):
# Get image, create the pixmap and resize to fit window
self.image = QImage(image_file)
self.setPixmap(QPixmap().fromImage(self.image))
self.resize(self.pixmap().size())
"""
Other methods used to customize the class, for example:
- Resize
- Rotate
- Crop
- ...
...
"""
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setAttribute(Qt.AA_DontShowIconsInMenus, True)
window = ImageView()
sys.exit(app.exec_())
When executed, yield a simple window with the image:
SIPI Test Image shown in PyQT5 Window
What we would like to do is add a mouseDoubleClickEvent to the imageQLabel sub-class, but ensure that other widgets or classes in the program can access the event signals. To do this, you can modify #ekhumoro's answer #2 to accommodate a pyqtSignal.
First, add the signal to the attributes of the imageQLabel class. Here, we need the full QMouseEvent rather than just an int:
import sys
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QImage, QPalette, QMouseEvent
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QScrollArea)
class imageQLabel(QLabel):
"""Subclass of QLabel for displaying image"""
# Class attributes
mouseDoubleClickSignal = pyqtSignal(QMouseEvent)
We wish to pass the event from that pyqtSignal to some other method in our ImageView main window class. As an example, let's make a method to print the cursor position when the user double clicks in the imageQLabel sub-class.
In ImageView:
class ImageView(QMainWindow):
"""
...
The rest of the class code
...
"""
def print_mouse(self, event):
print("print_mouse event from QMainWindow: {}".format((event.x(), event.y())))
Now we just need to connect print_mouse to the custom pyqtSignal we made. It makes the most sense to do this in the initializeUI method:
def initializeUI(self):
self.setMinimumSize(300, 200)
self.setWindowTitle("Image Viewer")
# self.showMaximized()
self.createMainQLabel()
# Connect the signal from custom imageQLabel class
# to the QMainWindow
self.image_qlabel.mouseDoubleClickSignal.connect(self.print_mouse)
self.show()
These modifications now print the cursor position when double clicking inside of the imageQLabel sub-class in the central widget. The call to print_mouse is outside of the sub-class. Moreover, double-clicking outside of the central widget (like outside of the frame of the image) does not call print_mouse because the mouseDoubleClickEvent is only active for the central widget.
%user profile%\AppData\Local\Programs\Python\Python39\python.exe
%script path%\stackoverflowsolution.py
print_mouse event from QMainWindow: (115, 140)
print_mouse event from QMainWindow: (54, 55)
override using python lambda magic
def on_key(line_edit : QLineEdit, key):
print(key)
...
line_edit = self._lineedit = QLineEdit()
line_edit.keyReleaseEvent = lambda ev, self=line_edit, super=line_edit.keyReleaseEvent: \
(super(ev), on_key(self, ev.key()), None)[-1]
We save current keyReleaseEvent function in lambda kwarg super.
Also we need to save line_edit in lambda kwarg self, because line_edit will be removed from locals during lambda execution.
Finally we need to return None, thus get it from last element from our Tuple.
I need to change the color of QPushButton, but an error occurred: "AttributeError: type object 'ProyectoTFM' has no attribute 'ui'".
I don't know hoy to acced to a ui variable from my thread.
This is my code:
import sys
import OpenOPC
import time
import threading
from proyectoQt import *
def actualizarDatosOPC():
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
ProyectoTFM.ui.AP08Button.setStyleSheet("background-color: red")
return
class ProyectoTFM(QtGui.QMainWindow):
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC)
threadQt.start()
def clienteOPC():
opc=OpenOPC.client()
opc.connect('Kepware.KEPServerEX.V6')
global itemsOPC
while 1:
itemsOPC = opc.read(opc.list('PLC.PLC.TAGS'))
time.sleep(5)
return
threads = list()
threadOPC = threading.Thread(target=clienteOPC)
threads.append(threadOPC)
threadOPC.start()
time.sleep(5)
if __name__== "__main__":
app=QtGui.QApplication(sys.argv)
myapp = ProyectoTFM()
myapp.show()
sys.exit(app.exec_())
threadOPC.__delete()
Sorry for my English and thanks.
It is not correct to modify the view from a different thread to the main one, a way to solve the problem without using QThread is to create a signal that connects to some slot that changes the color of the button. To be able to emit the signal from the new thread we must pass the object to him through the parameter args.
def actualizarDatosOPC(obj):
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
obj.sendChangeColor.emit()
return
class ProyectoTFM(QtGui.QMainWindow):
sendChangeColor = QtCore.pyqtSignal()
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
self.sendChangeColor.connect(lambda: self.ui.AP08Button.setStyleSheet("background-color: red"))
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC, args=(self,))
threadQt.start()
Even if you got this to work, you can't modify the UI from a thread directly.
A few things:
You never actually pass the UI to the function actualizarDatosOPC
so it doesn't know it exists.
Is there any reason you can't use PyQt's built in threading tools? If you are going to use PyQt it might make sense to buy into the whole framework.
def startTheThread(self):
self.threadQt = QThread()
d = actualizarDatosOPC(self)
d.moveToThread(self.threadQt)
self.threadQt.start()
def actualizarDatosOPC(widget):
.... widget.AP08Button.setStyleSheet("background-color: red")
If you do choose to go this route, I'd take a look at this thread which has a good example:
How to use QThread correctly in pyqt with moveToThread()?
Additionally, while the way you initialize your Window works, this is the more standard way to do it:
class ProyectoTFM(QMainWindow, Ui_MainWindow):
def __init__(self, parent):
# General Init Stuff
super(Login, self).__init__(parent)
self.setupUi(self)
After that, whenever you want to refer to something in the UI all you need to do is refer to self._____. For example, if you have a button named buttonA, self.buttonA would be the appropriate reference.
Edit:
As mentioned in another answer, the proper way to actually change the button color would be to emit a trigger that to the main thread which could then respond by changing the button color.
I am currently in the process of creating a class that produces a pyqtSignal(int) and pyqtSlot(int). The difficulty lies in creating a signal that emits a specific value.
Suppose I want to produce something similar to the following simple example:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
QVBoxLayout, QApplication)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def printLabel(self, str):
print(str)
#pyqtSlot(int)
def on_sld_valueChanged(self, value):
self.lcd.display(value)
self.printLabel(value)
def initUI(self):
self.lcd = QLCDNumber(self)
self.sld = QSlider(Qt.Horizontal, self)
vbox = QVBoxLayout()
vbox.addWidget(self.lcd)
vbox.addWidget(self.sld)
self.setLayout(vbox)
self.sld.valueChanged.connect(self.on_sld_valueChanged)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal & slot')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
My first question for the above code is:
Why use the pyqtSlot() decorator at all when removing pyqtSlot(int) has no effect on the code?
Can you give an example of when it would be necessary?
For specific reasons, I would like to produce my own signal using the pyqtSignal() factory and am given decent documentation here. The only problem however, is that the very simple example does not give a solid foundation for how to emit specific signals.
Here is what I am trying to do but have found myself lost:
Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
Have the metaclass produce its own signal that can be called from outside the class.
This is what I am going for:
from PyQt5.QtWidgets import QPushButton, QWidget
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QSlider
def template(Q_Type, name: str, *args):
class MyWidget(Q_Type):
def __init__(self) -> None:
super().__init__(*args)
self._name = name
def setSignal(self,type):
self.signal = pyqtSignal(type)
def callSignal(self):
pass
return MyWidget
As you can see. I give the widget a name because I find this to be useful, and I also try to instantiate the QWidget from within the class to simplify code.
This is how I would like a main class to produce the widgets from the first example:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
QVBoxLayout, QApplication)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def printLabel(self, str):
print(str)
#pyqtSlot(int)
def sld_valChanged(self, value):
self.lcd.display(value)
self.printLabel(value)
def initUI(self):
#instantiate the QWidgets through template class
self.lcd = template(QLCDNumber,'lcd_display')
self.sld = template(QSlider, 'slider', Qt.Horizontal)
#create signal
#self.sld.setSignal(int)
vbox = QVBoxLayout()
vbox.addWidget(self.lcd)
vbox.addWidget(self.sld)
self.setLayout(vbox)
#connect signal - this won't send the value of the slider
#self.sld.signal.connect(self.sld_valChanged)
self.sld.valueChanged.connect(self.sld_valChanged)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal & slot')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
There are only 2 problems that need to be solved here:
The template metaclass is set up incorrectly, and I am unable to instantiate the QWidget from within the metaclass. The Q_Type is telling me that its type is PyQt5.QtCore.pyqtWrapperType when it should be PyQt5.QtWidgets.QSlider.
Even though I am creating a newSignal via the templated class, how do I get the QSlider to send the changed value that I want it to send. I believe this is where emit() comes into play but do not have enough knowledge as to how it is useful.
I know I could make some sort of function call self.sld.emit(35) if I would like the signal to pass the value 35 to the slot function. The question becomes not how but where should I implement this function?
I may be totally offbase and overthinking the solution, feel free to correct my code so that I can have my signal emit the value of the slider.
Why use the pyqtSlot() decorator at all when removing pyqtSlot(int)
has no effect on the code? Can you give an example of when it would be
necessary?
Already addressed in your previous question, but I'll re-iterate again.
Although PyQt4 allows any Python callable to be used as a slot when
connecting signals, it is sometimes necessary to explicitly mark a
Python method as being a Qt slot and to provide a C++ signature for
it. PyQt4 provides the pyqtSlot() function decorator to do this.
Connecting a signal to a decorated Python method also has the
advantage of reducing the amount of memory used and is slightly
faster.
Also, as addressed in your previous question, you cannot create signals as instance variables, they must be class attributes.
This will never work
def setSignal(self,type):
self.signal = pyqtSignal(type)
It must be
class MyWidget(QWidget):
signal = pyqtSignal(int)
Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
You cannot define a metaclass for QObjects, since they already use their own metaclass (this is what allows the signal magic to work). However, you can still make a QObject subclass factory using the type function.
def factory(QClass, cls_name, signal_type):
return type(cls_name, (QClass,), {'signal': pyqtSignal(signal_type)})
MySlider = factory(QSlider, 'MySlider', int)
self.sld = MySlider(Qt.Horizontal)
self.sld.signal.connect(self.on_signal)
Have the metaclass produce its own signal that can be called from outside the class.
In short, don't do this. You shouldn't be emitting signals from outside the class. The factory example above isn't very useful, because you're not defining custom methods on those classes to emit those signals. Typically, this is how you would utilize custom signals
class MyWidget(QWidget):
colorChanged = pyqtSignal()
def setColor(self, color):
self._color = color
self.colorChanged.emit()
class ParentWidget(QWidget):
def __init__(self):
...
self.my_widget = MyWidget(self)
self.my_widget.colorChanged.connect(self.on_colorChanged)
# This will cause the colorChanged signal to be emitted, calling on_colorChanged
self.my_widget.setColor(Qt.blue)
def on_colorChanged(self):
# do stuff