Set ListView Model from C++ Code



  • Hi, I just started using Qt. So please bare with me.
    I'd like to use Qml QuickControls and mix it with C++ Code. My first project is a ListView with a Button underneath. The Button opens a file dialog and I can select a .csv table which contains a list of names and some information on them.

    The problem is, I have no idea, how to update the model of the listview from the C++ code. I'm using QQmlApplicationEngine (code follows below).

    So my question is basically how do I do this?
    I read some tutorials and info pages but there a lot of really confusing stuff the I can't get my head around.

    • How do I get access to QML objects? Have I done that correctly?
    • Do I always just use QQuickItem für every QML object? Why is there no corresponding ListView C++ class?
    • In some tutorials I read about setting context properties. But I couldn't quite translate that to the QQmlApplicationEngine approach. I tried using a property. Is that even possible?
    • Is it correct how I set up the Guest class with it's properties? And can cast that to a QVariant and set the views property?

    If the following is all totally wrong, could you please show me a good tutorial or give me some hints and tips? In general Qt seems to be very well documented, but I find the whole pure qml, QWidgets and mixing of everything hard to get my head around. Any help is appreciated, thanks!

    So far I have tried something like this:

    main.qml:

    import QtQuick 2.7
    import QtQuick.Controls 2.2
    import QtQuick.Layouts 1.3
    import QtQuick.Dialogs 1.2
    
    ApplicationWindow {
        id: applicationWindow
        objectName: "App"
        visible: true
        width: 640
        height: 480
        title: qsTr("Rosinenpicker")
    
        ColumnLayout {
            id: columnLayout
            anchors.rightMargin: 40
            anchors.leftMargin: 40
            anchors.bottomMargin: 40
            anchors.topMargin: 40
            anchors.fill: parent
    
    
            TextField {
                id: guestName
                text: qsTr("Text Field")
                Layout.preferredHeight: 50
                Layout.fillWidth: true
            }
    
            ListView {
                property ListModel guestModel
    
                id: guestList
                objectName: "guestView"
                Layout.fillHeight: true
                Layout.fillWidth: true
                model: guestModel
                delegate: guestDelegate
            }
    
            Component {
                id: guestDelegate
                Rectangle {
                    width: 50
                    height: 50
                    color: "red"
                    Text {
                        text: modelData
                    }
                }
            }
    
            Button {
                id: button
                width: 50
                text: qsTr("Open File")
                Layout.preferredWidth: 100
                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
    
                onClicked: fileDialog.visible = true;
            }
    
            FileDialog {
                id: fileDialog
                title: "Please choose a valid guestlist file"
                objectName: "fileDialog"
                nameFilters: ["Valid guestlist files (*.csv)"]
                selectMultiple: false
    
                signal fileSelected(url path)
    
                onAccepted: fileSelected(fileDialog.fileUrl)
            }
        }
    }
    

    main.cpp:

    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
        engine.load(QUrl(QLatin1String("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        auto root = engine.rootObjects().first();
        auto guestView = root->findChild<QQuickItem *>("guestView");
        auto fileDialog = root->findChild<QObject*>("fileDialog");
    
        CsvParser parser(guestView);
        QObject::connect(fileDialog, SIGNAL(fileSelected(QUrl)), &parser, SLOT(loadData(QUrl)));
    
        return app.exec();
    }
    
    #include "csvparser.h"
    #include "guest.h"
    
    #include <QtDebug>
    #include <QFile>
    
    CsvParser::CsvParser(QQuickItem *view)
    {
        this->view = view;
    }
    
    void CsvParser::loadData(QUrl path)
    {
        friendList.clear();
        guestList.clear();
    
        QFile file(path.toLocalFile());
        file.open(QIODevice::ReadOnly);
    
        // ignore first lines
        for(int i=0; i<3; i++)
            file.readLine();
    
        // parse file
        while(!file.atEnd()) {
            auto rowCells = file.readLine().split(',');
    
            if(rowCells.size() != 6)
                continue;
    
            checkedAdd(friendList, rowCells[0], rowCells[1]);
            checkedAdd(guestList, rowCells[3], rowCells[4]);
        }
    
        // set property of listview?
        view->setProperty("guestModel", QVariant::fromValue(guestList));
    }
    
    void CsvParser::checkedAdd(QList<QObject*> &list, QString name, QString familyName)
    {
        if(name == "" && familyName == "")
            return;
    
        list.append(new Guest(name, familyName));
    }
    

    guest.h:

    #ifndef GUEST_H
    #define GUEST_H
    
    #include <QObject>
    
    class Guest : public QObject
    {
    public:
        Q_OBJECT
    
        Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
        Q_PROPERTY(QString fName READ fName WRITE setfName NOTIFY fNameChanged)
    
    public:
        Guest(QString name, QString fName);
    
        QString name();
        QString fName();
    
        void setName(QString name);
        void setfName(QString fName);
    
    signals:
        void nameChanged();
        void fNameChanged();
    
    private:
        QString m_name, m_fName;
    };
    
    #endif // GUEST_H
    

    guest.cpp:

    Guest::Guest(QString name, QString fName)
    {
        setName(name);
        setfName(fName);
    }
    
    QString Guest::name()
    {
        return m_name;
    }
    
    QString Guest::fName()
    {
        return m_fName;
    }
    
    void Guest::setName(QString name)
    {
        m_name = name;
    }
    
    void Guest::setfName(QString fName)
    {
        m_fName = fName;
    }
    


  • @Rulpi said in Set ListView Model from C++ Code:

    Hi, I just started using Qt. So please bare with me.
    I'd like to use Qml QuickControls and mix it with C++ Code. My first project is a ListView with a Button underneath. The Button opens a file dialog and I can select a .csv table which contains a list of names and some information on them.

    The problem is, I have no idea, how to update the model of the listview from the C++ code. I'm using QQmlApplicationEngine (code follows below).

    So my question is basically how do I do this?
    I read some tutorials and info pages but there a lot of really confusing stuff the I can't get my head around.

    • How do I get access to QML objects? Have I done that correctly?
    • Do I always just use QQuickItem für every QML object? Why is there no corresponding ListView C++ class?
    • In some tutorials I read about setting context properties. But I couldn't quite translate that to the QQmlApplicationEngine approach. I tried using a property. Is that even possible?
    • Is it correct how I set up the Guest class with it's properties? And can cast that to a QVariant and set the views property?

    If the following is all totally wrong, could you please show me a good tutorial or give me some hints and tips? In general Qt seems to be very well documented, but I find the whole pure qml, QWidgets and mixing of everything hard to get my head around. Any help is appreciated, thanks!

    Usually and in general you don't need to mix QWidgets and QML. And you haven't done it in your code, you use only non-widget part of C++ Qt. You're just trying to do too much in C++. Usually and in general you shouldn't handle the QML UI in C++. You should write the C++ classes like it was a backend non-UI library which you control from QML. This part:

    auto root = engine.rootObjects().first();
        auto guestView = root->findChild<QQuickItem *>("guestView");
        auto fileDialog = root->findChild<QObject*>("fileDialog");
    
        CsvParser parser(guestView);
        QObject::connect(fileDialog, SIGNAL(fileSelected(QUrl)), &parser, SLOT(loadData(QUrl)));
    

    is unnecessary. You just have to create the CsvParser and set it as a context property (let's call it csvParser). CsvParser shouldn't know the guestView. You have also to set the view's C++ model object as a context property (let's call it guestModel). In this case you can use QStringListModel.

    Instead of

    signal fileSelected(url path)
    onAccepted: fileSelected(fileDialog.fileUrl)
    

    you should have

    onAccepted: {csvParser.loadData(fileDialog.fileUrl)}
    

    Instead of

    // set property of listview?
    view->setProperty("guestModel", QVariant::fromValue(guestList));
    

    you should just modify the model in C++, in this case using guestModel.setStringList(). Then, if you have removed the unnecessary

    property ListModel guestModel
    

    from the list view it should be updated automatically.

    This doesn't necessarily work out of the box, I just tried to find most of the relevant pieces. Feel free to ask for clarifications.



  • You can use QStringListModel for quick testing, but you may want to create a more complicated model for your data. I didn't notice at first that you have several text fields. There are many possibilities for a C++ model and it's necessary trivial. Try to get the architecture working first.



  • @Eeli-K
    Thanks for that very helpful answer. It helps a lot in understanding what's happening.

    You just have to create the CsvParser and set it as a context property (let's call it csvParser). CsvParser shouldn't know the guestView. You have also to set the view's C++ model object as a context property (let's call it guestModel).

    How would I do that? Could you maybe give me a minimal example of this?



  • @Rulpi See the simple examples in http://doc.qt.io/qt-5/qtqml-cppintegration-contextproperties.html. Or this:

        QApplication* app{new QApplication(argc, argv)};
        QQmlApplicationEngine* engine{new QQmlApplicationEngine()};
        QQmlContext* ctx{engine->rootContext()};
        ctx->setContextProperty("qApplication", app);
    

    And in QML you can use the object named qApplication everywhere, for example:

    Text {
        text: qApplication.applicationDisplayName //applicationDisplayName is a property of QApplication
    }
    

    Context property is good for objects which exist for the whole lifetime of the qml engine.



  • Thanks, you helped me a lot!


  • Lifetime Qt Champion

    Hi,

    Isn't that what the application QML object provides already ?



  • @SGaist QApplication was just my example. Only part or it is copied from real code, maybe it had something which application QML object doesn't have, I don't remember.


Log in to reply
 

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