Lier un widget PyQT/PySide à une variable locale en Python
Je suis assez nouveau à PySide / PyQt, je viens de C#/WPF. J'ai googlé beaucoup sur ce sujet, mais il a pas de bonne réponse semble montrer.
Je voudrais demander s'il y a un moyen de lier/connecter un QWidget à une variable locale, par lequel chaque objet se met à jour lui-même lors d'un changement.
Exemple: Si j'ai un QLineEdit
et j'ai une variable locale self.Name
dans une classe donnée, comment lier ces deux là quand un textChanged()
est déclenché ou simplement dire le changement de texte sur le QLineEdit
la variable est mise à jour et à la même époque, lorsque la variable est mise à jour QLineEdit
se mettre à jour sans appeler aucune méthode.
dans C# il y a une propriété de dépendance avec Convertisseurs et collection Observable pour la liste qui gère cette fonction.
je serai ravi si quelqu'un peut donner la réponse avec bon exemple
3 réponses
Vous demandons deux choses différentes ici.
- Vous voulez avoir un simple objet python,
self.name
abonnez-vous aux modifications sur unQLineEdit
. - Vous voulez avoir votre
QLineEdit
s'abonner à des changements sur une plaine objet pythonself.name
.
abonnez-vous aux modifications sur QLineEdit
est facile parce que c'est à cela que sert le système QT signal/slot. Vous venez de le faire comme ceci
def __init__(self):
...
myQLineEdit.textChanged.connect(self.setName)
...
def setName(self, name):
self.name = name
la partie la plus délicate est d'obtenir le texte dans le QLineEdit à changer quand self.name
changements. C'est délicat parce que self.name
n'est qu'un simple objet python. Il ne connaît rien aux signaux/slots, et python n'a pas de système intégré pour observer les changements sur les objets de la façon dont C# le fait. Vous pouvez toujours faire ce que vous voulez bien.
utilisez un getter / setter avec la fonctionnalité de propriété de Python
La chose la plus simple à faire est de faire self.name
Propriété. Voici un bref exemple du lien documentation (modifié pour plus de clarté)
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
Vous pouvez simplement ajouter une ligne pour mettre à jour l' QLineEdit
dans la méthode d'initialisation. De cette façon, chaque fois que quelque chose modifie la valeur de x
QLineEdit
sera mis à jour. Par exemple,
@name.setter
def name(self, value):
self.myQLineEdit.setText(value)
self._name = value
notez que les données du nom sont en fait conservées dans un attribut appelé _name
parce qu'il doit différer du nom du getter/setter.
utilisez un vrai système de rappel
la faiblesse de tous les c'est que vous ne pouvez pas facilement changer ce modèle observateur au moment de l'exécution. Pour ce faire, vous avez besoin de quelque chose comme ce que C# offre. Deux systèmes d'observateur de style C# en python sont obsub et mon projet observées. J'utilise observé dans mes propres projets pyqt avec beaucoup de succès. Notez que la version d'observed sur PyPI est derrière la version sur github. Je recommande la version github.
créez votre propre système de rappel simple
Si vous voulez pour le faire vous-même dans le plus simple possible vous ferait quelque chose comme ceci
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)
Maintenant, si vous venez de la sous-classe Observed
vous pouvez ajouter des callbacks à n'importe quelle méthode que vous voulez à l'exécution. Voici un exemple simple:
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
maintenant si vous implémentez le .name
propriété comme décrit ci-dessus, vous pouvez décorer le setter @event
et s'y abonner.
une autre approche serait d'utiliser une bibliothèque de publication-Abonnement comme pypubsub. Vous feriez QLineEdit s'abonner à un thème de votre choix (par exemple, " événement.nom') et chaque fois que votre code change de vous-même.nom vous sendMessage pour ce sujet (sélectionnez l'événement pour représenter ce nom change, comme ' roster.nom-changé"). L'avantage est que tous les auditeurs d'un sujet donné seront enregistrés, et QLineEdit n'a pas besoin de savoir précisément quel nom il écoute. Cet accouplement lâche c'est peut-être trop lâche pour toi, donc ce n'est peut-être pas approprié, mais je le jette comme une autre option.
aussi, deux gotchas qui sont stratégie spécifique à publier-subscribe (c'est-à-dire aussi pour l'obsub etc mentionné dans une autre réponse): vous pourriez vous retrouver dans une boucle infinie si vous écoutez QLineEdit qui se définit.le nom qui avertit les auditeurs.nom changé qui finit par appeler QLineEdit settext etc. Vous aurez besoin d'un garde ou vérifier que si moi-même.nom a déjà la valeur donnée de QLineEdit, ne rien faire; de même dans Qlinedit si le texte montré est identique à la nouvelle valeur de soi.nom alors ne le définissez pas de façon à ne pas générer de signal.
j'ai pris l'effort de concevoir un petit cadre générique de reliure à deux voies pour un projet pyqt sur lequel je travaille. Elle est ici: https://gist.github.com/jkokorian/31bd6ea3c535b1280334#file-pyqt2waybinding
voici un exemple d'utilisation (également inclus dans la gist):
la classe model (non-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)
la classe 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
l'instance se lie à valueChanged
signaux de la QSpinBox
instances et utilise la méthode setValue pour mettre à jour la valeur. Il comprend également comment se lier aux propriétés python, en supposant qu'il y ait un propertyNameChanged (naming convention) pyqtSignal correspondant sur l'instance de fin de liaison.
mise à jour je suis plus enthousiaste à ce sujet et a créé un bon de dépôt: https://github.com/jkokorian/pyqt2waybinding
Pour l'installer:
pip install pyqt2waybinding