我是PySide / PyQt的新手,我来自C#/ WPF。我已经在这个主题上搜索了很多,但似乎没有出现好的答案。
我想问有没有办法将QWidget绑定/连接到局部变量,每个对象在更改时自行更新。
示例:如果我有一个QLineEdit
,并且我在给定的类中有一个局部变量self.Name
,我该如何绑定这两个,从而触发textChanged()
或简单地说QLineEdit
变量上的文本更改已更新,同时,当更新变量时,QLineEdit
会更新而不调用任何方法。
在C#中有依赖项属性,转换器和Observable集合用于处理此函数的列表。
如果有人能用好的例子给出答案我会很高兴
答案 0 :(得分:19)
你在这里要求两件不同的东西。
self.name
订阅QLineEdit
上的更改。QLineEdit
订阅普通python对象self.name
上的更改。订阅QLineEdit
上的更改非常简单,因为这就是Qt信号/插槽系统的用途。你只是喜欢这个
def __init__(self):
...
myQLineEdit.textChanged.connect(self.setName)
...
def setName(self, name):
self.name = name
更棘手的部分是让QLineEdit中的文本在self.name
更改时更改。这很棘手,因为self.name
只是一个普通的python对象。它对信号/插槽一无所知,而且python没有内置系统来以C#的方式观察对象的变化。你仍然可以做你想做的事。
最简单的方法是将self.name
设为Property。以下是链接文档的简要示例(为清晰起见而进行了修改)
class Foo(object):
@property
def x(self):
"""This method runs whenever you try to access self.x"""
print("Getting self.x")
return self._x
@x.setter
def x(self, value):
"""This method runs whenever you try to set self.x"""
print("Setting self.x to %s"%(value,))
self._x = value
您可以添加一行来更新setter方法中的QLineEdit
。这样,只要有任何内容修改x
的值,QLineEdit
就会更新@name.setter
def name(self, value):
self.myQLineEdit.setText(value)
self._name = value
。例如
_name
请注意,名称数据实际上保存在名为import functools
def event(func):
"""Makes a method notify registered observers"""
def modified(obj, *arg, **kw):
func(obj, *arg, **kw)
obj._Observed__fireCallbacks(func.__name__, *arg, **kw)
functools.update_wrapper(modified, func)
return modified
class Observed(object):
"""Subclass me to respond to event decorated methods"""
def __init__(self):
self.__observers = {} #Method name -> observers
def addObserver(self, methodName, observer):
s = self.__observers.setdefault(methodName, set())
s.add(observer)
def __fireCallbacks(self, methodName, *arg, **kw):
if methodName in self.__observers:
for o in self.__observers[methodName]:
o(*arg, **kw)
的属性中,因为它必须与getter / setter的名称不同。
所有这一切的弱点在于您无法在运行时轻松更改此观察者模式。要做到这一点,你需要一些非常像C#提供的东西。 python中的两个C#样式观察器系统是obsub和我自己的项目observed。 我在自己的pyqt项目中使用了观察并取得了很大的成功。 请注意,PyPI上观察到的版本落后于github上的版本。我推荐github版本。
如果你想以最简单的方式自己做,你会做这样的事情
Observed
现在,如果您只是子类class Foo(Observed):
def __init__(self):
Observed.__init__(self)
@event
def somethingHappened(self, data):
print("Something happened with %s"%(data,))
def myCallback(data):
print("callback fired with %s"%(data,))
f = Foo()
f.addObserver('somethingHappened', myCallback)
f.somethingHappened('Hello, World')
>>> Something happened with Hello, World
>>> callback fired with Hello, World
,则可以在运行时向所需的任何方法添加回调。这是一个简单的例子:
.name
现在,如果您按上述方法实现@event
属性,则可以使用{{1}}修饰setter并订阅它。
答案 1 :(得分:3)
另一种方法是使用像pypubsub这样的发布 - 订阅库。您可以让QLineEdit订阅您选择的主题(例如,'event.name'),并且每当您的代码更改self.name时,您发送该主题的sendMessage(选择事件来表示正在更改的名称,例如'roster.name-changed “)。优点是给定主题的所有侦听器都将被注册,而QLineEdit不需要知道它侦听的具体名称。这种松散的耦合对你来说可能太松,所以它可能不合适,但我只是把它扔到那里作为另一种选择。
另外,两个问题不特定于发布 - 订阅策略(即,也适用于其他答案中提到的obsub等):如果你监听QLineEdit,你可能会陷入无限循环设置self.name,通知监听器self.name已更改,最终调用QLineEdit settext等。您将需要一个警卫或检查如果self.name已经具有QLineEdit给出的值,则不执行任何操作;类似地,在QLineEdit中,如果显示的文本与self.name的新值相同,则不要设置它,这样就不会生成信号。
答案 2 :(得分:1)
我已经努力为我正在研究的pyqt项目制作一个小的通用双向绑定框架。这是:https://gist.github.com/jkokorian/31bd6ea3c535b1280334#file-pyqt2waybinding
以下是一个如何使用它的例子(也包含在要点中):
class Model(q.QObject):
"""
A simple model class for testing
"""
valueChanged = q.pyqtSignal(int)
def __init__(self):
q.QObject.__init__(self)
self.__value = 0
@property
def value(self):
return self.__value
@value.setter
def value(self, value):
if (self.__value != value):
self.__value = value
print "model value changed to %i" % value
self.valueChanged.emit(value)
class TestWidget(qt.QWidget):
"""
A simple GUI for testing
"""
def __init__(self):
qt.QWidget.__init__(self,parent=None)
layout = qt.QVBoxLayout()
self.model = Model()
spinbox1 = qt.QSpinBox()
spinbox2 = qt.QSpinBox()
button = qt.QPushButton()
layout.addWidget(spinbox1)
layout.addWidget(spinbox2)
layout.addWidget(button)
self.setLayout(layout)
#create the 2-way bindings
valueObserver = Observer()
self.valueObserver = valueObserver
valueObserver.bindToProperty(spinbox1, "value")
valueObserver.bindToProperty(spinbox2, "value")
valueObserver.bindToProperty(self.model, "value")
button.clicked.connect(lambda: setattr(self.model,"value",10))
Observer
实例绑定valueChanged
个实例的QSpinBox
信号,并使用setValue方法更新值。它还理解如何绑定到python属性,假设绑定端点实例上有相应的propertyNameChanged(命名约定)pyqtSignal。
更新我对此更加热衷并为其创建了一个合适的存储库:https://github.com/jkokorian/pyqt2waybinding
安装:
pip install pyqt2waybinding