python pyqt dependency-properties pyside

Enlazando un widget PyQT/PySide a una variable local en Python



dependency-properties (3)

Soy bastante nuevo en PySide / PyQt, vengo de C # / WPF. He buscado mucho en este tema, pero parece que no aparece una buena respuesta.

Quiero preguntar si hay alguna manera en la que pueda enlazar / conectar un QWidget a una variable local, por lo que cada objeto se actualice en el cambio.

Ejemplo: Si tengo un QLineEdit y tengo una variable local self.Name en una clase determinada, ¿cómo puedo vincular estos dos mediante el cual cuando se textChanged() un textChanged() o simplemente digo el cambio de texto en el QLineEdit la variable se actualiza y en Al mismo tiempo, cuando se actualiza la variable, QLineEdit se actualiza sin llamar a ningún método.

En C # hay una propiedad de dependencia con conversores y una colección observable para la lista que maneja esta función.

Me alegrará si alguien puede dar una respuesta con buen ejemplo


Estás pidiendo dos cosas diferentes aquí.

  1. Desea tener un objeto python simple, self.name suscribirse a los cambios en un QLineEdit .
  2. Desea que su QLineEdit suscriba a los cambios en un objeto python simple self.name .

Suscribirse a los cambios en QLineEdit es fácil porque para eso sirve el sistema de señal / ranura Qt. Simplemente te gusta esto

def __init__(self): ... myQLineEdit.textChanged.connect(self.setName) ... def setName(self, name): self.name = name

La parte más complicada es hacer que el texto en el QLineEdit cambie cuando self.name cambia. Esto es complicado porque self.name es solo un objeto python simple. No sabe nada sobre señales / ranuras, y Python no tiene un sistema incorporado para observar los cambios en los objetos en la forma en que lo hace C #. Sin embargo, puedes hacer lo que quieras.

Utilice un getter / setter con la función de propiedad de Python

Lo más simple es hacer que self.name una propiedad . Aquí hay un breve ejemplo de la documentación vinculada (modificado para mayor claridad)

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

Simplemente podría agregar una línea para actualizar el QLineEdit en el método setter. De esta forma, cada vez que algo modifica el valor de x , se actualizará QLineEdit . Por ejemplo

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

Tenga en cuenta que los datos de nombre en realidad se mantienen en un atributo llamado _name porque tiene que diferir del nombre del getter / setter.

Use un sistema de devolución de llamada real

La debilidad de todo esto es que no puede cambiar fácilmente este patrón de observador en tiempo de ejecución. Para hacer eso necesitas algo realmente como lo que C # ofrece. Dos sistemas de observadores de estilo C # en Python son obsub y se observó mi propio proyecto. Uso observado en mis propios proyectos pyqt con mucho éxito. Tenga en cuenta que la versión de observado en PyPI está detrás de la versión en github. Recomiendo la versión de github.

Haga su propio sistema de devolución de llamada simple

Si quieres hacerlo tú mismo de la manera más simple posible, harías algo como esto

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)

Ahora bien, si solo subclase Observed , puede agregar devoluciones de llamada a cualquier método que desee en tiempo de ejecución. Aquí hay un ejemplo 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

Ahora bien, si implementa la propiedad .name como se describe arriba, puede decorar el setter con @event y suscribirse a él.


Me he tomado el trabajo de diseñar un pequeño marco vinculante genérico de 2 vías para un proyecto pyqt en el que estoy trabajando. Aquí está: https://gist.github.com/jkokorian/31bd6ea3c535b1280334#file-pyqt2waybinding

Aquí hay un ejemplo de cómo se usa (también incluido en la esencia):

La clase modelo (no 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 clase 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))

La instancia de Observer une a las señales valueChanged de las instancias de QSpinBox y utiliza el método setValue para actualizar el valor. También entiende cómo vincularse a las propiedades de python, suponiendo que hay una propiedad correspondienteChanged (convención de nomenclatura) pyqtSignal en la instancia de punto final de enlace.

actualización Me puse más entusiasta al respecto y creé un repositorio adecuado para él: https://github.com/jkokorian/pyqt2waybinding

Instalar:

pip install pyqt2waybinding


Otro enfoque sería usar una biblioteca de publicación y suscripción como pypubsub . Haría que QLineEdit se suscribiera a un tema de su elección (por ejemplo, ''event.name'') y siempre que su código cambie self.name, usted enviará Message para ese tema (seleccione evento para representar qué nombre está cambiando, como ''roster.name-changed ''). La ventaja es que todos los oyentes de un tema determinado se registrarán, y QLineEdit no necesita saber qué nombre escucha específico. Este acoplamiento flojo puede ser demasiado flojo para usted, por lo que puede no ser adecuado, pero lo estoy lanzando como otra opción.

Además, dos errores que no son específicos de la estrategia de suscripción-publicación (es decir, también para el obsub etc. mencionado en otra respuesta): podrías terminar en un bucle infinito si escuchas QLineEdit que establece self.name que notifica a los oyentes ese auto .name cambió, lo que termina llamando a QLineEdit settext, etc. Necesitarás un guardia o verificarás que si self.name ya tiene un valor dado por QLineEdit, no hagas nada; de manera similar en QLineEdit si el texto que se muestra es idéntico al nuevo valor de self.name, entonces no lo configure para que no genere una señal.