I wanted to ask if we could just animate the fontsize of the QLabel in PyQt5 with QPropertyAnimation.
I tried by adding QPropertyAnimation(self.label , b"fontSize") but It was not working and I got the warning
I want you increase the text of the QLabel and then again switch to normal size
From the QPropertyAnimation description:
QPropertyAnimation interpolates over Qt properties. As property values are stored in QVariants, the class inherits QVariantAnimation, and supports animation of the same meta types as its super class.
A class declaring properties must be a QObject. To make it possible to animate a property, it must provide a setter (so that QPropertyAnimation can set the property's value).
All classes that inherit from QObject (including widgets) support Qt properties, and in fact most of them have "builtin" properties, but, obviously, not all properties support animations: they must be based on numeric values. You can animate a numeric change (eg: from 0 to 100), not a text (eg. from 'ABC' to 'PYZ').
For instance, all QWidget have a pos property, and since such property (which is a QPoint) is based on numbers, you can create an animation. You can see the list of supported variant types for animations in the QVariantAnimation docs. Consider that all documentation pages regarding QObject subclasses contain a property section (see the QWidget properties for example), and that properties obviously are inherited from their super classes.
There is no property for the font size, though, so there are two possibilities:
create a custom property (by using the pyqtProperty decorator in PyQt);
use a QVariantAnimation, which is not bound to any property;
Depending on the situation, one might prefer one approach or the other, but for this specific case it doesn't change that much; the QVariantAnimation is certainly simpler, the Qt property approach is more "compliant".
Here you can see how the property is created using the pyqtProperty decorator. Since the animation writes the property, having a setter is mandatory.
class AnimationTest(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.startButton = QtWidgets.QPushButton('Start')
self.label = QtWidgets.QLabel('Hello!')
self.labelFont = self.font()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.startButton)
layout.addWidget(self.label)
self.ani = QtCore.QPropertyAnimation(self, b'labelFontSize')
self.ani.setStartValue(10)
self.ani.setEndValue(80)
self.ani.setDuration(1500)
self.startButton.clicked.connect(self.ani.start)
#QtCore.pyqtProperty(int)
def labelFontSize(self):
return self.labelFont.pointSize()
#labelFontSize.setter
def labelFontSize(self, size):
self.labelFont.setPointSize(size)
self.label.setFont(self.labelFont)
import sys
app = QtWidgets.QApplication(sys.argv)
test = AnimationTest()
test.show()
sys.exit(app.exec_())
The variant animation is certainly shorter:
class AnimationTest(QtWidgets.QWidget):
def __init__(self):
# ...
self.ani = QtCore.QVariantAnimation()
self.ani.setStartValue(10)
self.ani.setEndValue(80)
self.ani.setDuration(1500)
self.ani.valueChanged.connect(self.updateLabelFont)
self.startButton.clicked.connect(self.ani.start)
def updateLabelFont(self, value):
self.labelFont.setPointSize(value)
self.label.setFont(self.labelFont)
Please consider that font size animations are often problematic, as most fonts have slightly different glyphs and spacings depending on the size. In the examples above I used a pretty long animation that will probably show the effect: while the size increases linearly, the change in the font usually is not very smooth.
Related
I'm little confusing meaning of widgets instance creation in PyQt.
Seems QWidget(self) can create a widget.
But what's the meaning of QWidget(instance name)?
For exmaple,
grid_layout = QGridLayout(self)
test_label = QWidget(grid_layout)
I know test_label created inherited from gridlayout.
But what's the meaning during program meaning?
Is that meaning test_label widget is under grid_layout?
For many of the widgets when the signature is just a single parameter like in both of your examples, the argument given sets the created widget/layouts parent.
So in your first example, using self as the argument means that the parent of the QGridLayout and the widget that the layout will be applied to is whatever widget self is.
In the second example you are setting the parent of QWidget to be the widget represented by it's argument. A QWidget with no parent becomes a new window, when set with a parent then the widget becomes a child window within the parent widget.
Neither one are inheriting anything. Inheritance only occurs during Subclassing.
# code#1
import sys
from PyQt5.QtWidgets import QApplication, QPushButton
def onResize(event):
print("Nice to get here!")
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QPushButton('Test')
widget.resize(640, 480)
widget.show()
widget.resizeEvent = onResize
sys.exit(app.exec_())
The new resizeEvent is never fired in this code#1(when I resize the window manually).
# code#2
import sys
from PyQt5.QtWidgets import QApplication, QPushButton
def onResize(event):
print("Nice to get here!")
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QPushButton('Test')
widget.resize(640, 480)
widget.resizeEvent = onResize
widget.show()
sys.exit(app.exec_())
The new resizeEvent is nicely fired in this code#2(when I resize the window manually). And I can see msg printed out.
Does anyone know the reason? Even I add widget.update() and widget.show() after widget.resizeEvent = onResize in code#1, the resizeEvent code just keeps silence...
resizeEvent is a "virtual protected" method, and such methods should not be "overwritten" like this.
To implement them in a safe fashion, you'd better use subclassing:
class MyButton(QtWidgets.QPushButton):
def resizeEvent(self, event):
print("Nice to get here!")
Virtual methods are functions that are mostly used for subclasses.
When a subclass calls that method, it will look up in its class methods first.
If that method exists that method will be called, otherwise the MRO (as in Method Resolution Order) will be followed: simply speaking, "follow the steps back from the class to its inheritation(s) and stop when the method is found".
NOTE: in Python, almost everything is virtual, except things like "magic methods" ("dunder methods", the builtin methods that are enclosed within double underscores like __init__). With bindings like PyQt things are a bit more complex, and "monkey patching" (which completely relies on the virtual nature of Python) becomes less intuitive than it was.
In your case, the inheritance follows this path:
[python object]
Qt.QObject
QtWidget.QWidget
QtWidget.QAbstractButton
QtWidget.QPushButton
So, when you create QPushButton('Test') (calling its __init__(self, *args, **kwargs)), the path will be reversed:
__init__() a QtWidget.QPushButton with the new instance as first argument and the following "test" argument parameter;
QPushButton calls the inherited QAbstractButton class __init__(self, text), which gets the first argument after the instance as the button text;
QAbstractButton will call "some" of its methods or those of QWidget for size hints, font managing, etc.;
and so on...
In the same fashion, whenever yourButton.resizeEvent is called, the path will be reversed:
look for QtWidget.QPushButton.resizeEvent
look for QtWidget.QAbstractButton.resizeEvent
...
SIP (the tool created to generate python bindings for Qt) uses caching for virtuals, and as soon as a virtual [inherited] function is found, that function will always be called in the future if another Qt function requires it. This means that, as soon as no python override is found and the method lookup is successful, that method will always be used from that moment on, unless explicitly called (using "self.resizeEvent()" within your python code).
After calling show(), a QEvent.Resize is received by QWidget.event() (which is a virtual itself); if event() is not overwritten, the base class implementation is called, which will look up for the class resizeEvent function and call it with the event as argument.
Since, at that point, you've not overwritten it yet, PyQt will fallback to the default widget resizeEvent function, and from that point on it will always use that (according to the list above and QPushButton implementation, it will be the basic QWidget.resizeEvent call).
In your second example, show() is called after overwriting the resizeEvent function, allowing the event() function to "find" it (thus ignoring its base implementation along with those defined in the inherited classes) and will use yours until the program exits.
Also, in this case the method can be overwritten again, since SIP/Qt will not use caching anymore. This is a subtle but still very important difference to keep in mind: from this moment you can override that instance (note the bold characters) method as much as you want, as soon as you're certain it's not been called before!
def onResize1(event):
print("Nice to get here!")
def onResize2(event):
print("Can you see me?")
def changeResizeEvent(widget, func):
widget.resizeEvent = func
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QPushButton('Test')
widget.resize(640, 480)
# change the resize event function *before* it might be called
changeResizeEvent(widget, onResize1)
widget.show()
# change the function again after clicking
widget.clicked.connect(lambda: changeResizeEvent(widget, onResize2))
sys.exit(app.exec_())
As a rule of thumb, everything you see in the official Qt documentation that is labeled as virtual/protected generally requires a subclass to correctly override it. You can see that "[virtual protected]" text on the right of the resizeEvent() definition, and the function is also in the Protected Function list.
PS: with PyQt, some level of monkey patching works with classes too (meaning that you can overwrite class methods that will be automatically inherited by their subclasses), but that's not guaranteed and their behavior is often unexpected, especially due to the cross platform nature of Qt. It mainly depends on the class, its internal C++ behavior and inheritance(s), and how SIP relates to the original C++ objects.
Phil Thompson, the creator of PyQt talked about this in a thread.
PyQt caches lookups for Python reimplementations of methods. Monkey
patching will only work if you patch a method before it is looked up
for the first time. Your example will work for QWidget if you move the
call to show() after you patch it.
I think the best solution is to implement your own QPushButton subclass or assign it before show method as your code #2.
I am creating a custom widget that inherits from QLabel, and I would like to have a property on my widget to represent how the data must be formatted when presenting to the user.
For that I am trying to use Q_ENUMS, but I'm not having much success. I can get the property to show in Designer, but the UI file saved shows the enum as PyDMLabel::STRING and not as I would expect DisplayFormat::STRING.
Here is my code for the widget:
class PyDMLabel(QLabel, PyDMWidget):
class DisplayFormat:
DEFAULT = 0
STRING = 1
DECIMAL = 2
EXPONENTIAL = 3
HEX = 4
BINARY = 5
Q_ENUMS(DisplayFormat)
"""
A QLabel with support for Channels and more from PyDM
Parameters
----------
parent : QWidget
The parent widget for the Label
init_channel : str, optional
The channel to be used by the widget.
"""
def __init__(self, parent=None, init_channel=None):
QLabel.__init__(self, parent)
PyDMWidget.__init__(self, init_channel=init_channel)
self.setTextFormat(Qt.PlainText)
self.setTextInteractionFlags(Qt.NoTextInteraction)
self.setText("PyDMLabel")
self._display_format_type = PyDMLabel.DisplayFormat.DEFAULT
#pyqtProperty(DisplayFormat)
def displayFormat(self):
return self._display_format_type
#displayFormat.setter
def displayFormat(self, new_type):
if self._display_format_type != new_type:
self._display_format_type = new_type
What is the correct way to deal with Q_ENUMS and PyQt?
In order for Qt (Designer) to see an enum, PyQt has to add it to the meta-object of the custom class. So it could never be referred to by Qt as DisplayFormat::STRING.
In Qt, enums declared in the class scope expose their constants as members of the class. So for example, the QComboBox class defines an InsertPolicy enum, and the constants can be referred to like this: QComboBox::InsertAtTop. So in that respect, the behaviour of PyQt Q_ENUMS in Qt Designer plugins is exactly as expected, since the ui file shows PyDMLabel::STRING.
However, getting fully equivalent behaviour in Python code requires some extra work. The nearest I could come up with is this:
class DisplayFormat:
DEFAULT = 0
STRING = 1
DECIMAL = 2
EXPONENTIAL = 3
HEX = 4
BINARY = 5
class PyDMLabel(QLabel, PyDMWidget, DisplayFormat):
DisplayFormat = DisplayFormat
Q_ENUMS(DisplayFormat)
This will still result in Qt Designer using PyDMLabel::STRING (as expected). But Python code can now access the constants in any of these ways:
PyDMLabel.STRING
PyDMLabel.DisplayFormat.STRING
DisplayFormat.STRING
And in fact, if you don't mind losing the second of these options, you could simplify things even further to this:
class DisplayFormat:
DEFAULT = 0
...
class PyDMLabel(QLabel, PyDMWidget, DisplayFormat):
Q_ENUMS(DisplayFormat)
I am trying to display a QGraphicsView inside a QGraphicsWidget, but I can't seem to get it to work.
This is what I want to achieve, where the QGraphicsWidgets are all added to a QGraphicsScene, and each widget has their own QGraphicsScene and QGraphicsView.
The large box represents the main view, and the smaller boxes represent widgets, each with their own view and scenes.
My current implementation doesn't work, due to the following error:
TypeError: QGraphicsLinearLayout.addItem(QGraphicsLayoutItem):
argument 1 has unexpected type 'QGraphicsView'
My implemetation in a subclass of the QGraphicsWidget:
self.scene = QtGui.QGraphicsScene(self)
self.view = QtGui.QGraphicsView(self.scene)
self.view.setRenderHint(QtGui.QPainter.Antialiasing)
self.setLayout(QtGui.QGraphicsLinearLayout(Qt.Vertical))
self.layout().addItem(self.view)
I've tried to implement this with a QGraphicsLayoutItem, but I couldn't find anywhere to stick the QGraphicsView in.
Any help would be appreciated.
The function QGraphicsLinearLayout.addItem expects a QGraphicsWidget parameter and you are passing a normal QWidget (QGraphicsView).
You can use the class QGraphicsProxyWidget which is specially designed for wrapping QWidget into QGraphicsWidget.
I do not have Qt + Python on hands but I think it should look as follows:
proxy = parentView.addWidget(nestedView) # QWidget -> QGraphicsWidget
self.layout().addItem(proxy)
I'm making a GUI with pyqt4 and python. Right now I have a QLineEdit and QComboBox, where the QLineEdit displays the values and the QComboBox can be used to change units. I'm using signals and slots to handle real time unit/value feedback for the user but I'm having problems understanding how to programmatically work with the values as I need them all to be in standard units. Here's what I've got so far, the combo_box_line_edit_list is a list of list where I wrap the combo box and line list together
class UnitConverterSignaler(QtCore.QObject):
def __init__(self, combo_box_line_edit_list):
super(QtCore.QObject, self).__init__()
self.combo_box_line_edit_list = combo_box_line_edit_list
self.combo_box_list = [line_edit_combo_box[0] for line_edit_combo_box in combo_box_line_edit_list]
for combo_box, line_edit in self.combo_box_line_edit_list:
combo_box.currentIndexChanged['QString'].connect(line_edit.convert_units)
line_edit.store_unit_state(combo_box.currentText())
line_edit.standard_unit = combo_box.itemText(1)
def convert_to_standard(self):
for combo_box in self.combo_box_list:
combo_box.setCurrentIndex(0)
def convert_to_international(self):
for combo_box in self.combo_box_list:
combo_box.setCurrentIndex(1)
def toggle_unit_conversion(self, hold_line_values_steady):
for combo_box in self.combo_box_list:
if hold_line_values_steady:
combo_box.do_not_convert_units_on_change()
else:
combo_box.convert_units_on_change()
def convert_units_on_change(self):
"""
Changes the value of the line edit each time the combo box is changed
"""
for combo_box, line_edit in self.combo_box_line_edit_list:
combo_box.currentIndexChanged['QString'].connect(line_edit.convert_units)
combo_box.currentIndexChanged['QString'].disconnect(line_edit.store_unit_state)
def do_not_convert_units_on_change(self):
"""
Holds the line edit value constant in spite of combo box changes
"""
for combo_box, line_edit in self.combo_box_line_edit_list:
combo_box.currentIndexChanged['QString'].disconnect(line_edit.convert_units)
combo_box.currentIndexChanged['QString'].connect(line_edit.store_unit_state)
Instantiated & used in another class
self.lockCellCheckBox.toggled.connect(self.unit_converter_signaler.toggle_unit_conversion)
self.internationalPushButton.clicked.connect(self.unit_converter_signaler.convert_to_international)
self.standardPushButton.clicked.connect(self.unit_converter_signaler.convert_to_standard)
I've also monkey patched the QLineEdit instead of subclassing so I can make quick changes with QtDesigner.
# monkey patch slot onto line_edit
def convert_units(line_edit, end_unit):
converted_unit_value = line_edit.unit_registry.convert(float(line_edit.text()), line_edit.stored_unit_state, str(end_unit))
line_edit.setText(str(converted_unit_value))
line_edit.stored_unit_state = str(end_unit)
# monkey patch slot onto line_edit
def store_unit_state(line_edit, unit):
line_edit.stored_unit_state = str(unit)
Would the most generalized way to get the standard units out in my main program be the creation of a signal for each combo box/line edit in the UnitConverter?
From what I understood so far: you have many combo-box/line-edit pairs and the entered values should always be converted to standard units (e.g. displayed on a third QLabel or whatever).
Would the most generalized way to get the standard units out in my main program be the creation of a signal for each combo box/line edit in the UnitConverter?
No, you don't have to. A slot in python (or especially in pyqt) can be any callable object. A callable object is an object with method __call__(self) implemented.
Therefore I would suggest you to create a class which takes the related object(s) as parameter(s) in the contructor and changes them in __call__(self). Something like this:
class ConverterSignal:
def __init__(whatever_you_want_to_refer_to):
self.whatever_you_want_to_refer_to = whatever_you_want_to_refer_to
def __call(self)__:
""" Here you can refer to whatever_you_want_to_refer_to and do whatever you want with it """
The connection is done as following (for the combo box as an example):
self.connect(combo_box, QtCore.SIGNAL('activated(int)'), ConverterSignal(whatever_you_want_to_refer_to))
Here an instance of the class ConverterSignal is created and will be called if the corresponding signal is emitted.