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

12
demandé sur kevinarpe 2014-02-24 20:08:40

3 réponses

Vous demandons deux choses différentes ici.

  1. Vous voulez avoir un simple objet python, self.name abonnez-vous aux modifications sur un QLineEdit.
  2. Vous voulez avoir votre QLineEdit s'abonner à des changements sur une plaine objet python self.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.nameProprié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 xQLineEdit 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.

19
répondu DanielSank 2017-01-10 18:43:19

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.

3
répondu Oliver 2014-02-24 17:45:56

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
1
répondu jkokorian 2015-05-25 21:19:11