How to discern between user and programming generated events in QtQuick controls?



  • I'm doing some tests with QtQuick controls to find out how to use it in a bigger project.
    I'm controlling a webcam and i want to connect a QML spinbox to a C++ module so i can change the exposure time of the webcam. I successfully connected the spinbox so i can change the exposure and i can check on a QML label the state of the C++ property.
    Now i want to update the state of the spinbox if the exposure property changes in the C++ module. If i bind the two propertyes together the update happens only one way so i tried to use "onValueChanged".
    The problem is that the event "valueChanged" is emitted not only when the user interacts but also if the software changes the value. If i connect both events like:
    @
    //C++ module connected to the camera
    CppCore {
    id: cppCore
    onValueChanged: spinBoxExposure.value = value;
    }
    //QML control connected to the user
    SpinBox {
    id: spinboxExposure
    onValueChanged: cppCore.value = value;
    }
    @

    It all loops and go crazy :-) .

    In QtWidget i remember i avoided the problem using signals emitted only by user interaction or disconnecting the events when needed.

    How can i do the same in QML? I think this problem can surface in a other environments, how are you dealing with it?



  • You probably want to try to avoid the property binding being updated from both C++ and QML. Not just from a practical standpoint to avoid the infinite loop, but also for debugging sanity. :)

    One way I accomplished this for one of my projects was to consolidate the property updates in C++. I did this by exposing a read-only property from my C++ object to QML (done by omitting the WRITE attribute in the Q_PROPERTY declaration). QML would use this property to read the current value.

    I allowed QML to set this value by exposing a separate function from C++ marked as Q_INVOKABLE. This function, being in C++, could perform more complex error handling and other operations and would update the read-only property value as appropriate.

    Hopefully this approach could be applied to your use case as well.



  • Thanks for the reply.

    Currently (Qt5.1.1) binding a property between QML and C++ and change the value both side is safe because only the QML side is updated. At leas this is what i'm seeing in my tests with SpinBox and property binding:

    @
    webcam initialization => c++ => SpinBox => User
    User action => SpinBox | STOP
    @

    About your solution. I don't understand how your approach can avoid the loop if i connect the read-only property and the Q_INVOKABLE to the same QML control. Are you using two separate controls?

    For now i'm using a SpinBox as input and a Label as output but it's a quite bad solution. The spinbox is out of sync every time the camera rejects a value. Be able to stay up to date it's definitely an important feature for a graphical interface. I'ts like i'm missing some important detail here, i can't see a way to avoid the problem without adding a signal like "valueChangedByUser".



  • Hi ariacorrente,

    Here is what I was thinking (pseudo code, not compiled/tested):

    [code]
    // cppcore.h
    #include <QObject>

    class CppCore : public QObject
    {
    Q_OBJECT
    // Note: a read-only property (no WRITE attribute). This is the only exposure-related property shown in the QML GUI.
    Q_PROPERTY(qreal exposure READ exposure NOTIFY exposureChanged)

    public:
    CppCore(QObject* parent = nullptr) :
    QObject(parent),
    m_exposure(0.0)
    {

    }
    
    qreal exposure() const
    {
        return m_exposure;
    }
    

    public slots:
    // An separate exposure setter
    void commitExposure(const qreal exposure)
    {
    // if check needed to avoid infinite loop with the SpinBox's onValueChanged slot.
    if(m_exposure != exposure)
    {
    // do some error handling, other adjustments here.
    m_exposure = ;// some new value
    emit exposureChanged(m_exposure);
    }
    }

    private:
    qreal m_exposure;

    };
    [/code]

    QML:

    [code]
    import QtQuick 2.1
    import QtQuick.Controls 1.1

    Item {
    // ...

    SpinBox {
        // _cppCore is the C++ CppCore object exposed as a context property with setContextProperty().
        value: _cppCore.exposure
    
        onValueChanged: _cppCore.commitExposure(value)
    }
    

    }
    [/code]

    I'm thinking the above provides the primary benefit of allowing the property binding on the SpinBox's value property is be maintained throughout program execution (because no assignment (=) is used to modify its value).

    The if check in commitExposure() also ensures you avoid an infinite loop when the user modifies the exposure value using the SpinBox.

    Also, the C++ side is free to modify the exposure value it gets from the camera. All it has to do is ensure the exposureChanged signal is emitted to update it in QML.

    Would this work for your use case?



  • Thanks fonzi337, yours is a nice solution but unfortunately doesn't solve all my problems.
    [quote author="fonzi337" date="1386355198"]
    The if check in commitExposure() also ensures you avoid an infinite loop when the user modifies the exposure value using the SpinBox.
    [/quote]
    In commitExposure i can't check the current value because the camera code is running on another thread connected through signals.
    Maybe i can keep a copy of the state of the camera on the GUI thread and/or expose a camera object directly to QML.
    It sound like a lot of unneeded complications. It's the spinbox that knows the cause of the valueChange event, it's so strange this information is discarded and now i must guess it studying the variation of the values.

    Update:
    I'm continuing testing possible solutions so i update the post just in case someone else have a similar problem. I was trying to connect the camera object directly to QML to avoid too many signal/slot connections. This is the result:
    @QQmlEngine: Illegal attempt to connect to Camera(0x22fd88) that is in a different thread than the QML engine QQmlApplicationEngine(0x22fe70).@
    So I think this option is not viable.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.