Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding a PyQT/PySide widget to a local variable in Python

I'm pretty new to PySide/PyQt, I'm coming from C#/WPF. I've googled alot on this topic but it no good answer seems to show up.

Ii want to ask is there a way where I can bind/connect a QWidget to a local variable, whereby each object update themselves on change.

Example: If I have a QLineEdit and I have a local variable self.Name in a given class, how do I bind these two whereby when a textChanged() is triggered or simply say the text change on the QLineEdit the variable is updated and at the same time, when the variable is updated the QLineEdit get updated without calling any method.

In C# there is dependency property with converters and Observable collection for list that handles this function.

I will be glad if anyone can give answer with good example

like image 340
Temitayo Avatar asked Feb 24 '14 16:02

Temitayo


People also ask

Why use PySide instead of PyQt?

Advantages of PySide PySide represents the official set of Python bindings backed up by the Qt Company. PySide comes with a license under the LGPL, meaning it is simpler to incorporate into commercial projects when compared with PyQt. It allows the programmer to use QtQuick or QML to establish the user interface.

Are PyQt and PySide the same?

The key difference in the two versions — in fact the entire reason PySide2 exists — is licensing. PyQt5 is available under a GPL or commercial license, and PySide2 under a LGPL license.

Which is better PyQt or PySide?

PyQt is significantly older than PySide and, partially due to that, has a larger community and is usually ahead when it comes to adopting new developments. It is mainly developed by Riverbank Computing Limited and distributed under GPL v3 and a commercial license.

What is PySide used for?

PySide is a Python binding of the cross-platform GUI toolkit Qt developed by The Qt Company, as part of the Qt for Python project. It is one of the alternatives to the standard library package Tkinter. Like Qt, PySide is free software. PySide supports Linux/X11, macOS, and Microsoft Windows.


Video Answer


2 Answers

You're asking for two different things here.

  1. You want to have a plain python object, self.name subscribe to changes on a QLineEdit.
  2. You want to have your QLineEdit subscribe to changes on a plain python object self.name.

Subscribing to changes on QLineEdit is easy because that's what the Qt signal/slot system is for. You just do like this

def __init__(self):
    ...
    myQLineEdit.textChanged.connect(self.setName)
    ...

def setName(self, name):
    self.name = name

The trickier part is getting the text in the QLineEdit to change when self.name changes. This is tricky because self.name is just a plain python object. It doesn't know anything about signals/slots, and python does not have a built-in system for observing changes on objects in the way that C# does. You can still do what you want though.

Use a getter/setter with Python's property feature

The simplest thing to do is make self.name a Property. Here's a brief example from the linked documentation (modified for clarity)

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

You could just add a line to update the QLineEdit in the setter method. That way, whenever anything modifies the value of x the QLineEdit will be updated. For example

@name.setter
def name(self, value):
    self.myQLineEdit.setText(value)
    self._name = value

Note that the name data is actually being held in an attribute called _name because it has to differ from the name of the getter/setter.

Use a real callback system

The weakness of all of this is that you can't easily change this observer pattern at run time. To do that you need something really like what C# offers. Two C# style observer systems in python are obsub and my own project observed. I use observed in my own pyqt projects with much success. Note that the version of observed on PyPI is behind the version on github. I recommend the github version.

Make your own simple callback system

If you want to do it yourself in the simplest possible way you would do something like this

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)

Now if you just subclass Observed you can add callbacks to any method you want at run time. Here's a simple example:

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

Now if you implement the .name property as described above, you can decorate the setter with @event and subscribe to it.

like image 158
DanielSank Avatar answered Sep 18 '22 06:09

DanielSank


Another approach would be to use a publish-subscribe library like pypubsub. You would make QLineEdit subscribe to a topic of your choice (say, 'event.name') and whenever your code changes self.name you sendMessage for that topic (select event to represent what name is changing, like 'roster.name-changed'). The advantage is that all listeners of given topic will get registered, and QLineEdit does not need to know specific which name it listens to. This loose coupling may be too loose for you, so it may not be suitable, but I'm just throwing it out there as another option.

Also, two gotchas that are not specific to publish-subscribe strategy (i.e., also for the obsub etc mentioned in other answer): you could end up in an infinite loop if you listen for QLineEdit which sets self.name which notifies listeners that self.name changed which ends up calling QLineEdit settext etc. You'll either need a guard or check that if self.name already has value given from QLineEdit, do nothing; similarly in QLineEdit if text shown is identical to new value of self.name then don't set it so you don't generate a signal.

like image 41
Oliver Avatar answered Sep 17 '22 06:09

Oliver