pyqt5 小工具:pyqtProperty的便捷封装,用于打包读写函数和值改变信号

pyqt5 pyqtProperty的便捷封装,用于打包读写函数和值改变信号

  • 背景
    • 传统的pyqtProperty定义方式
  • 实现思路
  • 适用场景
  • 源码
  • 执行结果

cpp版戳这里

背景

  • 项目用到了pyqt,需要使用qt动态属性来暴露控件的一些样式属性,以便于在qss中一次性解决主题问题

传统的pyqtProperty定义方式

  • 如下,非常的繁琐,大量都是相同逻辑的重复代码,属性一旦多了就非常不美观,如果是C++ 用Qt Creator还能通过编辑器自动生成一下,pyqt暂时没有找到类似的便捷方法只能手敲,而且python也没有宏这样的代码替换工具,于是以此为动机需要找一个方法来封装冗余的代码块

 class Meal(QObject):

    def __int__(self):
        super(Meal, self).__int__()
       	self.temperature = 0
        
	def setTemperature(self, value: int):
		if value== self.temperature:
			return
		self.temperature = value
		self.temperatureChanged.emit(self.temperature)

	def temperature(self)
		return self.temperature 

	temperatureChanged = pyqtSignal(int)
    temperature = pyqtProperty(type=int, fget=temperature, fset=setTemperature)

网上关于pyqtProperty的描述很少,看来看去就几篇帖子,还是从官方的简陋文档翻译的,看得我头疼,索性就自己研究一下。

实现思路

  1. 从pyqtProperty派生,将setter和getter以及信号都封装到该类中,然后像这样去使用;
  class Meal(QObject):

    def __int__(self):
        super(Meal, self).__int__()

    temperature = QtNotifyProperty(int)

世界都清净了,比C++的定义都清晰。

适用场景

  • 凡是需要定义setter getter signal这三要素的属性的地方,一行代码替代
  • 在同类实例较少的情况下使用,如UI类,单例类等;这是因为实现使用的是Python的Dict,并且是定义在类的静态空间中;一个该类实例对应一个属性,如果同类实例过多的话会影响效率。

源码

from PyQt5.QtCore import *


class QtNotifyProperty(pyqtProperty):
    class PropertyObject(QObject):
        changed = pyqtSignal(QVariant)

        def __call__(self, *args, **kwargs):
            return self._value

        def __init__(self, PropertyType):
            super(QtNotifyProperty.PropertyObject, self).__init__()
            self._value = PropertyType()

        def set(self, value):
            if value == self._value:
                return
            self._value = value
            self.changed.emit(self._value)

        def bind(self, obj: QObject, propertyName: str, onChange: 'def (src: srcType)->destType' = None):
            if onChange is None:
                self.changed.connect(lambda value: obj.setProperty(propertyName, value))
            else:
                self.changed.connect(lambda value: obj.setProperty(propertyName, onChange(value)))

        def get(self):
            return self._value

    def __set__(self, instance, value):
        self.setPropertyValue(instance, value)

    def __get__(self, instance, owner) -> PropertyObject:
        return self.getPropertyObj(instance)

    def __init__(self, propertyType):
        self._properties: dict[QObject, QtNotifyProperty.PropertyObject] = {}

        def _getPropertyObj(owner: QObject) -> QtNotifyProperty.PropertyObject:
            try:
                return self._properties[owner]
            except KeyError:
                self._properties[owner] = \
                    self.PropertyObject(propertyType)

                def removeProperty():
                    del self._properties[owner]
                    print("remove")

                owner.destroyed.connect(removeProperty)
                return self._properties[owner]

        def _getPropertyValue(owner: QObject):
            return _getPropertyObj(owner)()

        def _setPropertyValue(owner: QObject, value) -> None:
            _getPropertyObj(owner).set(value)

        self.getPropertyObj = _getPropertyObj
        self.setPropertyValue = _setPropertyValue
        super(QtNotifyProperty, self).__init__(type=propertyType, fget=_getPropertyValue,
                                               fset=_setPropertyValue)


class Meal(QObject):

    def __int__(self):
        super(Meal, self).__int__()

    temperature = QtNotifyProperty(int)
    calorie = QtNotifyProperty(int)


def main():
    a = QCoreApplication([])
    meal = Meal()
    meal2 = Meal()
    # 直接 属性.信号名 的方式获取信号
    meal.temperature.changed.connect(lambda value: print("meal on temperature changed:", value))
    meal.calorie.changed.connect(lambda value: print("meal on calorie changed:", value))
    meal2.temperature.changed.connect(lambda value: print("meal2 on temperature changed:", value))
    meal2.calorie.changed.connect(lambda value: print("meal2 on calorie changed:", value))
    # 属性绑定,将meal2和meal对象进行默认的属性绑定
    meal.temperature.bind(meal2, "temperature")
    meal.calorie.bind(meal2, "calorie")
    # setProperty的方式触发值改变信号
    meal.setProperty("temperature", 50)
    meal.setProperty("calorie", 1000)
    # property的方式访问属性值
    print("temperature: ", meal.property("temperature"))
    print("calorie: ", meal.property("calorie"))
    # 赋值的方式触发值改变信号
    meal.temperature = 60
    meal.calorie = 2000
    # 获取属性值的另外两种方式 .get() 和 ()
    print("temperature", meal.temperature())
    print("calorie", meal.calorie.get())


if __name__ == '__main__':
    main()

执行结果

pyqt5 小工具:pyqtProperty的便捷封装,用于打包读写函数和值改变信号_第1张图片

你可能感兴趣的:(Qt,pyqt)