将PyQT / PySide小部件绑定到Python中的局部变量

时间:2014-02-24 16:08:40

标签: python pyqt dependency-properties pyside

我是PySide / PyQt的新手,我来自C#/ WPF。我已经在这个主题上搜索了很多,但似乎没有出现好的答案。

我想问有没有办法将QWidget绑定/连接到局部变量,每个对象在更改时自行更新。

示例:如果我有一个QLineEdit,并且我在给定的类中有一个局部变量self.Name,我该如何绑定这两个,从而触发textChanged()或简单地说QLineEdit变量上的文本更改已更新,同时,当更新变量时,QLineEdit会更新而不调用任何方法。

在C#中有依赖项属性,转换器和Observable集合用于处理此函数的列表。

如果有人能用好的例子给出答案我会很高兴

3 个答案:

答案 0 :(得分:19)

你在这里要求两件不同的东西。

  1. 您希望拥有一个普通的python对象,self.name订阅QLineEdit上的更改。
  2. 您希望QLineEdit订阅普通python对象self.name上的更改。
  3. 订阅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#的方式观察对象的变化。你仍然可以做你想做的事。

    使用具有Python属性功能的getter / setter

    最简单的方法是将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

以下是一个如何使用它的例子(也包含在要点中):

模型(非gui)类

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)

QWidget(gui)类

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