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的描述很少,看来看去就几篇帖子,还是从官方的简陋文档翻译的,看得我头疼,索性就自己研究一下。
实现思路
- 从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()