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
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.
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.
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.
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.
You're asking for two different things here.
self.name
subscribe to changes on a QLineEdit
.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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With