Piatus w=10=wt BC w=12=wt 标准 w=11=wt

PyQt's "pyqtProperty" VS python's standard "property"

我有几个关于 PyQt5 包的 pyqtProperty class 和 python 的标准内置 property class 的问题:

  1. 为什么要使用 pyqtProperty 而不是标准的 属性?下面给出的代码以两种方式工作。使用 pyqtProperty 而不是 Python 的普通旧 property(用作方法的装饰器)有什么好处?我知道我必须指定 pyqtProperty 的 return 类型,但我为什么要使用它?

  2. pyqtProperty 是否继承自 property?属性 pyqtProperty.__mro__ 没有显示(结果是 PyQt5.QtCore.pyqtProperty, object),我找不到 pyqtProperty 的源代码文件。 PyDoc's command refers to a .pyd file of the module PyQt5.QtCore (which is not a source code plain text file), and there is no stub file for it. But the command help(pyqtProperty) gives me information very similar to the one get with help(property), different from the one found on the official documentaion。那么,这些信息从何而来?

  3. 为什么@pyqtProperty甚至@property修饰的方法在设置Qobject时执行?

    回到下面给出的代码:属性 方法(在 @pyqtProperty@property 两种情况下)在创建属性 _custom_property 之前执行,它给出一个AttributeError。但这不应该发生,并且 @property 装饰器和常规 python classes 确实不会发生(我也试过了,下面有一个片段)。当我使用简单的 getter 和 setter 方法(未修饰)时,根本不会发生这种情况(这是正常的)。最奇怪的现象是:我在调试模式下运行时出现这个AttributeError。在正常模式下 (python main.py) 属性 方法无论如何都会执行,但不会引发错误。

我使用的是 PyQt5 版本 5.9.2,安装了 Anaconda Distribution,python 3.8.


这是Ui_MainWindow.py文件的内容,用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"))

这是使用 property 装饰器的 main.py 文件的内容:

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_()

这是使用 pyqtProperty 装饰器的 main.py 文件的内容:

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_()

我不会把没有装饰器的主文件的内容放在上面(只需删除上面代码中的 @ 行)。
但这是一个带有 property 装饰器的简单文件,表明它没有在 __init__ 方法

中执行
class Test:
    
    def __init__(self) -> None:
        pass
    
    @property
    def x(self):
        print("This method is not executed in init")
        return self._x
    
    def setx(self,value):
        self._x = value

if __name__ == "__main__":
    a = Test()
  1. pyqt属性和属性的区别不是优势,就是这些属性会可以通过 Qt 拥有的元对象访问,最后一个工具用于 Qt 中的各种任务,例如在 QML 中公开属性,能够在 QtStyleSheet 中使用,在 QPropertyAnimation 中使用等。

  2. property as pyqtProperty 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.

  3. custom_property 方法调用是由 QMetaObject::connectSlotsByName() 引起的,但我认为这是一个错误。如果方法名称满足特定模式:on_<object name>_<signal name>(<signal parameters>),则此方法会尝试建立语义连接。我指出这是一个错误,因为如果我使用 PySide2 实现相同的逻辑,我不会观察到该行为。

eyllanesc 的回答足够完整,但我想通过解释一些实际需要使用 pyqtProperty 而使用标准 python 属性是不够的情况来扩展它:

Qt 样式表

QSS 提供对 Qt 属性的访问:

  1. 对于selectors,可以定义一个属性是否匹配指定值的规则;例如,您可以更改所有标记为 OkCancel:
  2. 的按钮的颜色
QPushButton[text="Ok"] {
    color: green;
}
QPushButton[text="Cancel"] {
    color: red;
}

请注意,更改定义外观的 属性 的值时小部件已经显示(通过样式“抛光”)需要再次重新计算样式 sheet。在上述情况下,如果将按钮的文本从“确定”更改为“取消”,您将看不到文本颜色有任何变化。为了实现这一点,需要通过obj.setStyleSheet(obj.styleSheet())重新设置样式sheet,这可以在小部件本身上完成 任何设置了样式 sheet 的父项(包括 QApplication)。
最简单的方法是在 属性 的 setter 中执行 self.setStyleSheet(self.styleSheet()) (可能,在检查值是否实际更改之后),即使小部件具有 no 样式 sheet 设置并且规则在其任何祖先上定义。

  1. 可以设置属性,只要它们的类型被 QSS 支持:
#Window {
    qproperty-minimumSize: 300px 300px;
    qproperty-geometry: rect(250 250 400 400);
}

请注意,正如 documentation 所解释的那样,“q属性 语法仅被评估一次,即当小部件被样式抛光时”,因此您不能使用伪设置属性-状态(如 :hover 状态),因为它们可能会被忽略或提供意外和不可靠的行为。

项目委托编辑者

默认情况下,项目委托使用 user property.

设置编辑器的值
class MyCustomEditor(QtWidgets.QWidget):
    # ...
    QtCore.pyqtProperty(object, user=True)
    def myCoolProperty(self):
        # ...

这意味着如果你有一个自定义编辑器并使用一个声明了 user 标志的 Qt 属性,你只需要实现 createEditor() return 自定义编辑器(甚至可以在自定义 QItemEditorFactory 中为特定数据类型定义编辑器),而 setEditorData() 将自动设置其值,而无需担心索引、数据类型或属性 姓名。

设计器插件

这更像是一个边缘场景,因为很少需要为设计器创建插件,而且不是一件容易的事(PyQt 文档很少而且大部分已经过时,一些未实现为 C++ 提供的特性和功能)。但是,如果您确实需要它,在使用插件创建的自定义小部件的 属性 编辑器框中显示自定义属性的唯一方法是使用 Qt 属性。