I have a few questions regarding the pyqtProperty
class of PyQt5 package, and the standard built-in property
class of python:
Why use
pyqtProperty
over standard property? The codes given below works in both ways. What's the advantage of usingpyqtProperty
instead of Python's plain oldproperty
(used as a decorator on a method)? I know that I must specify the return type of apyqtProperty
, but why would I want to use it?Does
pyqtProperty
inherits fromproperty
? The attributepyqtProperty.__mro__
does not show that (the result isPyQt5.QtCore.pyqtProperty, object
) and I can't find a source code file ofpyqtProperty
. PyDoc's command refers to a.pyd
file of the modulePyQt5.QtCore
(which is not a source code plain text file), and there is no stub file for it. But the commandhelp(pyqtProperty)
gives me information very similar to the one get withhelp(property)
, different from the one found on the official documentaion. So, where that information comes from?Why the methods decorated with
@pyqtProperty
or even with@property
are executed when the Qobject is set up?Back to the codes given below: the property methods (in the both cases
@pyqtProperty
and@property
) are executed before creating the attribute_custom_property
, which gives aAttributeError
. But that should not happen, and really does not happen with@property
decorator with regular python classes (I've tried that too, there's a snipet below). When I use simple getter and setter methods (not decorated), that doesn't happen at all (which is normal). The strangest phenomenon is: thisAttributeError
occurs when I run in debug mode. In normal mode (python main.py
) the property methods get executed anyway, but the error is not raised.
I'm using PyQt5 version 5.9.2 installed with Anaconda Distribution, python 3.8.
This is the contents of Ui_MainWindow.py file, generated with pyuic5:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(419, 196)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setAlignment(QtCore.Qt.AlignCenter)
self.lineEdit.setObjectName("lineEdit")
self.horizontalLayout.addWidget(self.lineEdit)
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.horizontalLayout.addWidget(self.pushButton)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 419, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.lineEdit.setText(_translate("MainWindow", "test"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
This is the content of the main.py
file using property
decorator:
from Ui_MainWindow import Ui_MainWindow
from PyQt5.QtWidgets import QApplication, QMainWindow
class MainWindow(QMainWindow,Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self._custom_property = 1
@property
def custom_property(self):
print("This method is executed")
return self._custom_property
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
This is the content of the main.py
file using pyqtProperty
decorator:
from Ui_MainWindow import Ui_MainWindow
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import pyqtProperty
class MainWindow(QMainWindow,Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self._custom_property = 1
@pyqtProperty(int)
def custom_property(self):
print("This method is executed")
return self._custom_property
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
I won't put the content of the main file with no decorator (just remove the @
lines in the code above).
But this is a simple file with property
decorator showing it is not executed in the __init__
method
class Test:
def __init__(self) -> None:
pass
@property
def x(self):
"This method is not executed in init"
return self._x
def setx(self,value):
self._x = value
if __name__ == "__main__":
a = Test()
CodePudding user response:
The difference between pyqtProperty and property, not an advantage, is that these properties will be accessible through the metaObject that Qt has, and this last tool is used for various tasks in Qt such as exposing properties in QML, being able to be used in QtStyleSheet, being used in QPropertyAnimation, etc.
property
aspyqtProperty
is a descriptor so it should not necessarily inherit from property (here is an example of how to create descriptors without inheriting from property). If you want to know the implementation of pyqtProperty then you should check the qpycore_pyqtproperty.h and qpycore_pyqtproperty.cpp file in the qpy/QtCore folder of the source code.The
custom_property
method call is caused byQMeraObject::connectSlotsByName()
but I think it is a bug. This method tries to make the semantic connection if the name of the method meets a certain pattern:on_<object name>_<signal name>(<signal parameters>)
. I point out that it is a bug since if I implement the same logic with PySide2 I do not observe that behavior.
CodePudding user response:
The answer by eyllanesc is complete enough, but I'd like to extend it by explaining some situations for which using a pyqtProperty
is actually required and using standard python properties won't suffice:
Qt StyleSheets
QSS provide access to Qt properties:
- for selectors, it's possible to define a rule whether a property matches the specified value; for instance, you could change the color of all buttons that are labelled
Ok
orCancel
:
QPushButton[text="Ok"] {
color: green;
}
QPushButton[text="Cancel"] {
color: red;
}
Note that changing the value of a property that defines appearance when the widget has already been displayed ("polished" by the style) requires to recompute the stylesheet again. In the above case, if you change the text of the button from "Ok" to "Cancel", you won't see any change in the text color. In order to achieve that, it's required to re-set the stylesheet by means of obj.setStyleSheet(obj.styleSheet())
, which can be done on the widget itself or any of the parents that have a stylesheet set (including the QApplication).
The easiest way is to do self.setStyleSheet(self.styleSheet())
in the setter of the property (possibly, after checking if the value has actually changed), which will work even if the widget has no style sheet set and the rule is defined on any of its ancestors.
- it's possible to set properties, as long as their types are supported by QSS:
#Window {
qproperty-minimumSize: 300px 300px;
qproperty-geometry: rect(250 250 400 400);
}
Note that, as the documentation explains, "the qproperty syntax is evaluated only once, which is when the widget is polished by the style", so you cannot set properties with pseudo-states (like the :hover
state) as they will probably be ignored or provide unexpected and unreliable behavior.
Item delegate editors
By default, item delegates set the value of an editor using its user property.
class MyCustomEditor(QtWidgets.QWidget):
# ...
QtCore.pyqtProperty(object, user=True)
def myCoolProperty(self):
# ...
This means that if you have a custom editor and use a Qt property declared with the user flag, you only need to implement createEditor()
to return the custom editor (or even define the editor in a custom QItemEditorFactory for specific data types), and setEditorData()
will automatically set its value without worrying about the index, the data type or the property name.
Designer plugins
This is more of a fringe scenario, as creating plugins for designer is rarely needed and it is not an easy task (the PyQt documentation is scarce and mostly outdated, and some features and functions provided for C are not implemented). But, if you do need it, the only way to have custom properties shown in the property editor box of a custom widget created with a plugin is to use Qt properties.