在Python中将PyQT/PySide小部件绑定到本地变量

2024-03-28 12:39:06 发布

您现在位置:Python中文网/ 问答频道 /正文

我对PySide/PyQt很陌生,我来自C#/WPF。我在谷歌上搜索了很多关于这个话题的信息,但似乎没有一个好的答案出现。

我想问的是,有没有一种方法可以将QWidget绑定/连接到一个局部变量,从而每个对象在发生更改时更新自己。

示例:如果在给定的类中有一个QLineEdit,并且有一个局部变量self.Name,那么如何绑定这两个变量,即当textChanged()被触发时,或者简单地说QLineEdit上的文本更改时,变量将被更新,同时,当变量被更新时,将在不调用任何方法的情况下更新QLineEdit

在C#中,有一个依赖属性,其中包含处理此函数的转换器和列表的可观察集合。

如果有人能以身作则,我会很高兴的


Tags: 对象方法答案nameself信息示例pyqt
3条回答

你在这里要两样东西。

  1. 您想要一个普通的python对象,self.name订阅QLineEdit上的更改。
  2. 您想让您的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#那样观察对象的变化。但你仍然可以做你想做的事。

使用带有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的属性中,因为它必须与getter/setter的名称不同。

使用真正的回调系统

所有这些的缺点是,您不能在运行时轻松地更改这个观察者模式。要做到这一点,你需要一些真正像C提供的东西。python中的两个C风格的观察者系统是obsub和我自己的项目observed。 我在自己的pyqt项目中使用了observated,并取得了很大的成功。 注意,PyPI上观察到的版本落后于github上的版本。我推荐github版本。

创建自己的简单回调系统

如果你想用最简单的方法自己做,你会做这样的事情

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)

现在,如果您只是子类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装饰setter并订阅它。

我已经为一个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实例绑定到QSpinBox实例的valueChanged信号,并使用setValue方法更新该值。它还了解如何绑定到python属性,假设绑定端点实例上有相应的propertyNameChanged(命名约定)pyqtSignal。

更新我对它更感兴趣,并为它创建了一个合适的存储库:https://github.com/jkokorian/pyqt2waybinding

要安装:

pip install pyqt2waybinding

另一种方法是使用像pypubsub这样的发布-订阅库。您可以让QLineEdit订阅您选择的主题(例如,“event.name”),并且每当代码更改self.name时,您就发送该主题的消息(选择event来表示正在更改的名称,例如“flower.name changed”)。其优点是,给定主题的所有侦听器都将被注册,QLineEdit不需要知道它所侦听的具体名称。这个松耦合对你来说可能太松了,所以它可能不合适,但我只是把它作为另一个选择扔掉。

另外,有两个问题是不是特定于发布-订阅策略(即。,另外,对于其他答案中提到的obsub etc):如果您侦听QLineEdit,它设置self.name,通知侦听器self.name已更改,最后调用QLineEdit settext等,则可能会出现无限循环。您需要一个保护,或者检查self.name是否已经具有来自QLineEdit的值,则不执行任何操作;类似地,在QLineEdit如果显示的文本与self.name的新值相同,则不要设置它,这样就不会生成信号。

相关问题 更多 >