0

I'm working on a QML project. In the UI I'm working on, I need to both update slider from C++ and read the current value into C++ from QML. Properties seems to be the right solution. So far I've read different questions on SO without success Two way binding C++ model in QML, Changed Properties do not trigger signal, etc... In my current code I declared a property in my C++ class

class MyClass : public QObject {
    Q_OBJECT
public:
    MyClass(QObject*);
    Q_PROPERTY(double myValue READ getMyValue WRITE setMyValue NOTIFY myValueChanged)

    void setMyValue(double n) {
        std::cerr << "myValue  being update: " << n << "\n";
        myValue = n;
    }

    double myValue = 30;
...
}

And exposed it into Qt via a singleton

qmlRegisterSingletonInstance("com.me.test", 1, 0, "MyClass", &myClass);

Then bound the C++ property to a QML slider

import com.me.test
ApplicationWindow {
    Slider {
        id: slider
        height: 30
        width: 100
        from: 0
        to: 100
        value: myClass.myValue
        onValueChanged {
            console.log("value = " + value)
            console.log("myClass.myValue = " + myClass.myValue)
        }

        /* Doesn't help
        Binding {
            target: slider
            property: "value"
            value: myClass.myValue
        }*/
    }
}

The binding seems to work. I can modify the value of myValue then emit myValueChanged to make QML update it's slider. But given that myClass.myValue is bounded to slider.value. I'd assume both values gets updated at the same time. But dragging the slider shows that they have different values. The following it what is printed in the console when I drag my slider.

qml: value = 19.863013698630137
qml: myClass.myValue = 30

Furthermore setMyValue seems to not being called unless an explicit assignment is made like myClass.myValue = 0. I also tried the Binding component without success. Why is this the case and could I make the C++ property updated whenever i drag the slider?

Qt: 6.2.1
Compiler: clang/gcc
OS: Windows/Linux

Update: tested a reverse binding. Still printing the same result

import com.me.test
ApplicationWindow {
    Slider {
        id: slider
        height: 30
        width: 100
        from: 0
        to: 100
        value: myClass.myValue
        onValueChanged {
            console.log("value = " + value)
            console.log("myClass.myValue = " + myClass.myValue)
        }
        Binding {
            target: myClass
            property: "myValue"
            value: slider.value
        }
    }
}
Mary Chang
  • 865
  • 6
  • 25
  • You bind `value` to `myClass.myValue` but not vice versa. I mean when you bind A to B that doesn't create implicit bindings B to A. Otherwise, you know, If that were the case so changing A will notify B that, in turn, will notify A and so on indefinitely. On the other hand when you bind A to B so A should be notified when the B changes and the set A manually it will break the bindings. – folibis Nov 24 '21 at 08:31

3 Answers3

1

You're missing the signal when modifying the value:

Q_PROPERTY(double myValue READ getMyValue WRITE setMyValue NOTIFY myValueChanged)

This means that you promise you'll send the myValueChanged signal whenever the C++ code changes the value. So the following should work:

void setMyValue(double n) {
    std::cerr << "myValue  being update: " << n << "\n";
    myValue = n;
    emit(myValueChanged());
}
Vincent Fourmond
  • 3,038
  • 1
  • 22
  • 24
0

To update C++ property from QML, you can use Binding:

import com.me.test
ApplicationWindow {
    Slider {
        id: slider
        height: 30
        width: 100
        from: 0
        to: 100
        value: myClass.myValue
        onValueChanged {
            console.log("value = " + value)
            console.log("myClass.myValue = " + myClass.myValue)
        }
         // C++ property was bounded to QML above, now we should bind QML to C++
        Binding {
            target: myClass 
            property: "myValue"
            value: slider.value
        }
    }
}
  • what happens when `myClass.myValue` will be updated and will fire its `myValueChanged`? – folibis Nov 24 '21 at 09:20
  • @folibis It will update the slider value' property. Of course, the `setMyValue` method should start with comparison between a new value and the current value, and emit `myValueChanged` only if the value was really updated. In other case, you could see a binding loop – Jakub Warchoł Nov 24 '21 at 09:34
  • Thanks for the reply. I tried the suggested solution. But still not seeing the setter called nor the value of `console.log("myClass.myValue = " + myClass.myValue)` changed. – Mary Chang Nov 24 '21 at 09:44
  • @MaryChang In the above code, you will see `console.log("myClass.myValue = " + myClass.myValue)` when you will move a slider. To see `myClass.myValue` value changed, have a look at [Connections](https://doc.qt.io/qt-5/qml-qtqml-connections.html) – Jakub Warchoł Nov 24 '21 at 09:55
0

Here's a minimal working example of two way updating Slider:

main.cpp:

data dataObj;
engine.rootContext()->setContextProperty("dataCpp", (QObject*)&dataObj);

data.h:

class data : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
    explicit data(QObject *parent = nullptr);
    int value(void);
    void setValue(int new_value);
public slots:
    void reset(void);
signals:
    void valueChanged();
private:
    int dataValue;
};

data.cpp:

data::data(QObject *parent) : QObject(parent)
{
    dataValue = 250;
}

int data::value()
{
    return dataValue;
}

void data::setValue(int new_value)
{
    if(dataValue != new_value)
    {
        qDebug() << "setting" << new_value;
        dataValue = new_value;
        emit valueChanged();
    }
}

void data::reset()
{
    if(dataValue != 0)
    {
        qDebug() << "resetting to 0";
        dataValue = 0;
        emit valueChanged();
    }
}

main.qml:

Slider {
    id: slider
    height: 50
    width: 500
    from: 0
    to: 500
    live: false
    value: dataCpp.value
    onValueChanged: dataCpp.value = value
}

Button{
    anchors.top: slider.bottom
    text: "Reset"
    onPressed: dataCpp.reset()
}
Arun Kumar B
  • 196
  • 9