Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Simplest way for two-way binding a text field



  • Hi, I'm having trouble to have updates sent to a bound property without explicitly defining an event handler.

    class Model(QtCore.QObject):
        """simplest model with a text property"""
    
        def __init__(self):
            super().__init__()
            self._member_a = 'YES'
    
        def a(self):
            return self._member_a
    
        def set_a(self, v):
            print(f'changing to {v}')
            self._member_a = v
    
        @QtCore.Signal
        def a_changed(self):
            pass
    
        a = QtCore.Property(str, a, set_a, notify=a_changed)
    
    
    model = Model()
    
    app = QtGui.QGuiApplication(sys.argv)
    view = QtQuick.QQuickView()
    view.rootContext().setContextProperty('model', model)
    
    view.setResizeMode(QtQuick.QQuickView.SizeRootObjectToView)
    with Path('Main.qml').open('w') as f:
        f.write('''
            import QtQuick 2.0
            import QtQuick.Controls 2.0
            TextField {
              text: model.a
              // I don't want this: 
              // onEditingFinished: model.a = text
            }''')
    
    view.setSource('Main.qml')
    view.resize(100, 100)
    view.show()
    result = app.exec_()
    

    My setup is with PySide6 6..0.1:

    $ poetry show
    astroid           2.5    An abstract syntax tree for Python with inference support.
    isort             5.7.0  A Python utility / library to sort Python imports.
    lazy-object-proxy 1.5.2  A fast and thorough lazy object proxy.
    mccabe            0.6.1  McCabe checker, plugin for flake8
    pylint            2.7.0  python code static checker
    pyside6           6.0.1  Python bindings for the Qt cross-platform application and UI framework
    shiboken6         6.0.1  Python / C++ bindings helper module
    toml              0.10.2 Python Library for Toms Obvious, Minimal Language
    wrapt             1.12.1 Module for decorators, wrappers and monkey patching.
    

    I tried to find proper guidance in the documentation, but failed.

    Is there a better way than to define the onEditingFinished handler?

    Thanks!



  • @xtofl In QML the binding is unidirectional: from right to left:

    Foo {
         id: foo
         a: bar.b
    }
    

    This binding indicates that every time the property "b" (which must be a signal) of the object "bar" then the property "a" of the object "foo" will be updated with that value. In python it would be something like:

    bar.b_Signal.connect(lambda: setattr(foo, "a", foo.b))
    

    If you want to implement a 2-way binding then create a class that listens when one of those properties change ("a" or "b") and then updates the other.


  • Lifetime Qt Champion

    Hi,

    @xtofl said in Simplest way for two-way binding a text field:

    def set_a(self, v):
    print(f'changing to {v}')
    self._member_a = v

    You are not emitting the change signal.

    The classic implementation is:

    def set_a(self, value):
        if self._member_a == value:
            return
        self._member_a = value
        self.a_changed.emit(value)
    


  • Thank you. Only... this makes things even less simple. Are you saying that this is the simplest way?


  • Lifetime Qt Champion

    In what way is it less simple ?

    The only simpler would be:

    def set_a(self, value):
        if self._member_a != value:
            self._member_a = value
            self.a_changed.emit(value)
    


  • Thanks - the emit is a necessary addition, I agree. I meant that adding a line by definition makes code less simple.

    But what I really don't understand is why I would need the onEditingFinished handler in the qml itself. I was hoping to get the 2-way binding working without explicit event handling, by just binding Textfield.text property to model.a property.



  • @xtofl In QML the binding is unidirectional: from right to left:

    Foo {
         id: foo
         a: bar.b
    }
    

    This binding indicates that every time the property "b" (which must be a signal) of the object "bar" then the property "a" of the object "foo" will be updated with that value. In python it would be something like:

    bar.b_Signal.connect(lambda: setattr(foo, "a", foo.b))
    

    If you want to implement a 2-way binding then create a class that listens when one of those properties change ("a" or "b") and then updates the other.


Log in to reply