0

I am new to QtQuick controls, I would like to create a simple GUI and I have encountered the following problem with binding certain c++ variables to qml widgets properties: In the simplified example below, I have a single combobox for which I would like to assign a list of items and also be able to set which item is selected from within the c++ side of the application.

For the test_list/model pair everything is working exactly as I would expect, but for the test_index/currentIndex it is not. After the test_index is set to 2 and notification is called, nothing happens - I expect that the READ method (getIndex) should be called, but it is not (it is for the test_list though). Changing the index from the GUI works, i.e. the value is sent from the qml to the c++ side due to the onCurrentIndexChanged, but it does not work the other way around. So after calling the modifyValues(), there are four items in the combobox - 0, 1, 2, 3 - but the currentIndex is still 0, so the "0" item is selected (meanwhile, test_index is equal to 2).

Odd thing is that the getIndex does get called the first time the widget is created. I have read that changing a property in the qml will terminate all the declarative bindings, but I do not see why this should happen here, especially when it works for the 'model' property (or other that I tried, e.g. currentText). Nevertheless, I have tried to put myCombo.currentIndex = testClassInstance.test_index to the onCurrentIndexChanged as well, but that does not help (and it does not seem right...).

My questions: Is there something special about the currentIndex property? How to achieve what I want without resorting to explicitly defining signal connections in the qml (I thought that this is basically what the Q_PROPERTY macro is for)? I think I could figure out some workarounds to get to my goal, so that is not what I am looking for here - instead, I would prefer if somebody could explain to me why this particular approach is not working the way I expect.

main.qml

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Dialogs 1.2

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")


    MainForm {
        objectName: "mainForm"
        anchors.fill: parent
    }
}

MainForm.qml

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1

Item {
   width: 640
   height: 480


   Rectangle {
      anchors.centerIn: parent

      ComboBox {
            id: myCombo
            model: testClassInstance.test_list
            currentIndex: testClassInstance.test_index
            visible: true
            onCurrentIndexChanged: {
                testClassInstance.test_index = myCombo.currentIndex
            }
       }
   }
}

testClass.h

#ifndef TESTCLASS_H
#define TESTCLASS_H

#include <QObject>

class testClass : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int test_index READ getIndex WRITE setIndex NOTIFY notification)
    Q_PROPERTY(QStringList test_list READ getList WRITE setList NOTIFY notification)

public:
    testClass(QObject *parent) : QObject(parent) {}
    ~testClass() {}

    void modifyValues()
    {
        test_list << "1" << "2" << "3";
        notification();
        test_index = 2;
        notification();
    }

    int getIndex() { return test_index;  }
    QStringList getList() { return test_list;   }

    void setIndex(int val) { test_index = val;  }
    void setList(QStringList val) { test_list = val; }

signals:
    void notification();

private:
    int test_index = 0;
    QStringList test_list = QStringList("0");

};

#endif

main.cpp

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QQmlApplicationEngine engine;

    testClass test(&engine);
    engine.rootContext()->setContextProperty("testClassInstance", &test);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    test.modifyValues();

    return app.exec();
}
tomj
  • 1,089
  • 7
  • 17

2 Answers2

2

Lets break in steps:

1) You create combobox object and bind it's properties (index and model) to class' properties

2) You call modifyValues and a) change list, this triggers model changing b) change index, this triggers... nothing

The problem is on step 2a. ComboBox inside has following code:

onModelChanged: {
    ...
    popup.__selectedIndex = 0
}

This call breaks your binding for index, so when you go to step 2b, binding don't exist anymore.

And here is solution to your problem, you should updage binding after model changed:

onModelChanged: {
     currentIndex = Qt.binding( function(){return testClassInstance.testIndex} )
}
Volodymyr K.
  • 669
  • 8
  • 20
  • Oh...that actually makes sense. I have to say though, even if I realized this on my own, I don't think I would have figure out that I have to use the `Qt.binding(...)` syntax instead of just something like `currentIndex: test_index`, this seems quite unintuitive and I didn't see that used in any of the tutorials I came across...Nevertheless, thank you very much for both the explanation and solution. – tomj Feb 27 '16 at 12:01
  • Whenever you write signal handler code, it is javascript and simple assignment won't work as binding, so you have to create binding explicitly. – Volodymyr K. Feb 27 '16 at 12:18
0

As a quick reply, you can use Connections,

   ComboBox {
       id: myCombo
       model: testClassInstance.test_list
       visible: true
       onCurrentIndexChanged: {
           testClassInstance.test_index = myCombo.currentIndex
       }
       Connections {
           target: testClassInstance
           onNotification: myCombo.currentIndex = test_index;
       }
   }
cavitsinadogru
  • 940
  • 2
  • 10
  • 17
  • Hi, thank you, this solution is working (I actually tried it before, just didn't know that I have to specify the "onNotification:" there). Anyway, I would still like to know why I have to do this for the currentIndex yet I don't need to do it for the "model", "currentText" or bunch of other properties; that is quite confusing for me. I will wait a few days if somebody (or you if you have the time) can explain that, if nobody does I will mark your answer as accepted. – tomj Feb 26 '16 at 16:29
  • Hello, you can always consult to Qt documents. They are well documented. It is already described here, http://doc.qt.io/qt-5/qml-qtquick-controls-combobox.html#model-prop – cavitsinadogru Feb 29 '16 at 08:31
  • I have read that earlier, but the issue is that "Changing the model after initialization will reset currentIndex to 0." in my eyes did not imply that the binding to another variable gets broken. I would expect based on this sentence that the currentIndex will be set to 0 AND the variable bound to it will also be set to 0. By now I have learned that this is not how it works (and it actually makes sense when one takes more time to think about it), but I didn't realize it before. Maybe it would be worth explicitly mentioning the "breaking bindings" problem there for the "less experienced" people. – tomj Feb 29 '16 at 12:28
  • Yes you are actually right, also property binding on qml is a little bit tricky on the first experience, assignment breaks the binding, but one expect is assign it but not broke the binding. Cheers. – cavitsinadogru Feb 29 '16 at 13:18