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

Problems with setting model to ListView from another class



  • Hi,

    I'm using Qt 5.15 with MinGW to develop an app. My app consists of a StackView and with some buttons you can navigate through different pages. I have one page that shows the available bluetooth devices found and here begins the problems, I'm sure I'm doing something wrong but I can't understand what.

    This is my main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include "dispositivi.h"
    #include "mainbackend.h"
    #include "devicessqlmodel.h"
    #include "availabledevicesmodel.h"
    
    static QObject *mySingleObject(QQmlEngine *engine, QJSEngine *scriptEngine)
    {
        Q_UNUSED(engine)
        Q_UNUSED(scriptEngine)
    
        MainBackend *backend = new MainBackend();
        return backend;
    }
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
    
        qmlRegisterSingletonType<MainBackend>("com.company.mainbackend", 1, 0, "MainBackend" , mySingleObject);
        qmlRegisterType<Dispositivi>("com.company.dispositivi", 1, 0, "Dispositivi");
        qmlRegisterType<DevicesSqlModel>("com.company.models.devices", 1, 0, "DevicesSqlModel");
        qmlRegisterType<AvailableDevicesModel>("com.company.models.availabledevices", 1, 0, "AvailableDevicesModel");
    
        QQmlApplicationEngine engine;
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        engine.load(url);
    
    
        return app.exec();
    }
    
    

    I have a singletontype that is accesible from all QML file. Then I have C++ class Dispositivi that must be initializated only when we are on the Device page. The device page code is this:

    import QtQuick 2.0
    import QtQuick.Controls 2.12
    import QtQuick.Layouts 1.12
    import com.starlane.dispositivi 1.0
    
    Item {
        Rectangle {
            id: rectangle
            x: 220
            y: 197
            width: 200
            height: 200
            color: "#000022"
            border.width: 0
            anchors.fill: parent
    
            Dispositivi {
                id: device
                onScanHasFinished: {
                    busyIndicatorId.running = false;
                    console.log("Scan Has Finished")
                }
            }
    
    
            TextInput {
                id: textDevices
                height: 20
                color: "#ffffff"
                text: qsTr("Dispositivi Disponibili Per l'abbinamento:")
                font.bold: true
                font.family: "Arial"
                verticalAlignment: Text.AlignVCenter
                horizontalAlignment: Text.AlignHCenter
                anchors.right: parent.right
                anchors.rightMargin: 0
                anchors.left: parent.left
                anchors.leftMargin: 0
                anchors.top: parent.top
                anchors.topMargin: 0
                font.pixelSize: 14
            }
    
    
            Rectangle {
                id: rectangleAddDevice
                x: 566
                y: 406
                width: 64
                height: 64
                color: "#000033"
                radius: 20
                anchors.right: parent.right
                anchors.rightMargin: 10
                anchors.bottom: parent.bottom
                anchors.bottomMargin: 10
                border.width: 2
                border.color: "#ffffff"
    
                Image {
                    id: imageAddDevice
                    anchors.rightMargin: 10
                    anchors.leftMargin: 10
                    anchors.bottomMargin: 10
                    anchors.topMargin: 10
                    anchors.fill: parent
                    source: "qrc:/Image/img/add.png"
                    fillMode: Image.PreserveAspectFit
                }
    
                MouseArea {
                    id: mouseAreaAddDevice
                    anchors.fill: parent
                    onClicked: {
                        stackView.push("qrc:/SearchForBluetoothDevices.qml")
                    }
                }
            }
    
    
            Rectangle {
                id: rectangleBack
                x: 10
                y: 406
                width: 64
                height: 64
                color: "#000033"
                radius: 20
                anchors.left: parent.left
                anchors.leftMargin: 10
                anchors.bottom: parent.bottom
                anchors.bottomMargin: 10
                border.width: 2
                border.color: "#ffffff"
    
                Image {
                    id: imageBack
                    anchors.rightMargin: 10
                    anchors.leftMargin: 10
                    anchors.bottomMargin: 10
                    anchors.topMargin: 10
                    anchors.fill: parent
                    source: "qrc:/Image/img/left.png"
                    fillMode: Image.PreserveAspectFit
                }
    
                MouseArea {
                    id: mouseAreaBack
                    anchors.fill: parent
                    onClicked: {
                        if (stackView.depth > 1)
                            stackView.pop()
                    }
                }
            }
    
    
            ListView {
                id: listView
                anchors.bottom: rectangleBack.top
                anchors.bottomMargin: 10
                anchors.right: parent.right
                anchors.rightMargin: 0
                anchors.left: parent.left
                anchors.leftMargin: 0
                anchors.top: textDevices.bottom
                anchors.topMargin: 10
                model: device.devicesModel     //here is the problem
    
                delegate: Rectangle {
                    x: 5
                    width: listView.width
                    height: 50
                    RowLayout {
                        id: row1
                        Rectangle {
                            width: parent.width / 3 * 2
                            height: 30
                            color: "white"
    
                            Text {
                                id: textName
                                anchors.fill: parent
                                text: name
                                height: 30
                                anchors.verticalCenter: parent.Center
                                font.bold: true
                                color: "black"
                            }
                        }
    
                        Rectangle {
                            id: rectanglePair
                            width: parent.width / 3
                            height: 30
                            Text {
                                id: buttonPair
                                text: qsTr("Abbina")
                                anchors.fill: parent
                            }
    
                            MouseArea {
                                id: mouseAreaPair
                                anchors.fill: parent
                                onClicked: {
                                    console.log("Abbinare: " + textName.text)
                                }
                            }
                        }
                    }
                }
    
                BusyIndicator {
                    id: busyIndicatorId
                    anchors.fill: parent
                    running: true
                }
            }
        }
    
        Component.onCompleted: {
            busyIndicatorId.running = true
            device.startScan()
        }
    
    
    }
    

    I've also tried to update the model using the onScanFinished slot but obviously it didn't work.
    When the page is created the signal startScan() is emitted and the c++ class Dispositivi starts the device discovery.
    Now when the scan is finished I would like to see the list of the devices using the model property of ListView.

    This is the Dispositivi.h

    #include <QObject>
    #include <QBluetoothLocalDevice>
    #include <QBluetoothDeviceDiscoveryAgent>
    #include <QVector>
    
    QT_FORWARD_DECLARE_CLASS(QBluetoothDeviceInfo)
    
    #include "availabledevicesmodel.h"
    
    QT_USE_NAMESPACE
    
    class Dispositivi : public QObject
    {
        Q_OBJECT
    public:
        explicit Dispositivi(QObject *parent = nullptr);
    
        Q_INVOKABLE void signalFromQml();
        Q_INVOKABLE void startScan();
        AvailableDevicesModel devicesModel;
    
    public slots:
        void scanFinished();
        void addDevice(const QBluetoothDeviceInfo &info);
        
    signals:
        void scanHasFinished();
    
    private:
        QBluetoothDeviceDiscoveryAgent *discoveryAgent;
        QBluetoothLocalDevice *localDevice;
        QStringList availableDevices;
    
    };
    
    

    and this is the Dispositivi.cpp

    #include "dispositivi.h"
    #include <QDebug>
    
    Dispositivi::Dispositivi(QObject *parent) : QObject(parent)
    {
        qDebug() << "Inizializzazione Dispositivi";
        availableDevices.clear();
    
        discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
    
        connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), this, SLOT(addDevice(QBluetoothDeviceInfo)));
        connect(discoveryAgent, SIGNAL(finished()), this, SLOT(scanFinished()));
    }
    
    void Dispositivi::signalFromQml()
    {
        qDebug() << "Arrivato il segnale";
    }
    
    void Dispositivi::startScan()
    {
        discoveryAgent->start();
    }
    
    void Dispositivi::addDevice(const QBluetoothDeviceInfo &info)
    {
        bool found = false;
        QString label = QString("%1 %2").arg(info.address().toString()).arg(info.name());
        for (int i = 0; i < availableDevices.size(); i++)
        {
            if(availableDevices.at(i) == label)
            {
                found = true;
                break;
            }
        }
        if (!found)
        {
            availableDevices.append(label);
            qDebug() << "Dispositivo trovato:" << label;
        }
    }
    
    void Dispositivi::scanFinished()
    {
        qDebug() << "Finito lo scan";
        for(int i = 0; i < availableDevices.size(); i++)
        {
            devicesModel.insertDevice(availableDevices.at(i));
        }
        emit scanHasFinished();
    }
    

    To create AvailableDevicesModel I followed the Docs

    #include <QAbstractListModel>
    
    class DeviceDiscovered
    {
    public:
        DeviceDiscovered(const QString &deviceName, const QString &macAddress)
        {
            this->deviceName = deviceName;
            this->macAddress = macAddress;
        }
        QString device() const
        {
            return deviceName;
        }
        QString macAddr() const
        {
            return macAddress;
        }
    
    private:
        QString deviceName;
        QString macAddress;
    };
    
    class AvailableDevicesModel : public QAbstractListModel
    {
        Q_OBJECT
    
    public:
        explicit AvailableDevicesModel(QObject *parent = nullptr);
    
        enum {
            DeviceRole = Qt::UserRole,
            MacAddrRole
        };
    
        void insertDevice(QString deviceName);
    
        void addDevice(const DeviceDiscovered &device);
    
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    
        Qt::ItemFlags flags(const QModelIndex &index) const override;
    
        virtual QHash<int, QByteArray> roleNames() const override;
        
    private:
        QList<DeviceDiscovered> m_devices;
    };
    

    Now somehow I have to set the devicesModel as the model of the ListView.
    Everything works fine but I really can't understand how to set the ListView model property when the model has to be created in runtime and not in the main.cpp as the examples do. I'm sorry if this is a trivial question, I'm pretty new to qml and probably in my code there are tons of errors (feel free to report them, it would be really appreciated) :)

    Thanks in advance!


  • Qt Champions 2016

    @davidesalvetti while searching for devices I show a modal popup with ProgressBar
    as soon as all devices are scanned from C++ I'm emitting a signal deviceDiscoveryDone()
    in QML I'm connected to the C++ Signal, close popup and set listView model (from C++ Q_INVOKABLE method)


  • Qt Champions 2018

    @davidesalvetti said in Problems with setting model to ListView from another class:

    model: device.devicesModel //here is the problem

    What's wrong with it?

    It should work as is, given that you correctly send the signal about new rows or data changes in your model.


  • Qt Champions 2016

    @GrecKo of course you're right - should work if correct signals are emitted for device.devicesModel

    In my case I'm not using a QAbstractListModel, but only a simple QList<MyDevice*> where MyDevice is a QObject* providing Address, Name... - don't need all the overhead around QAbstractList - I'm only looking for some specific SPP Devices (Scanner, Printer) where the User has to select the one he/she wants to work with



  • @davidesalvetti said in Problems with setting model to ListView from another class:

    devicesModel

    Is not a property that QML can see. Look up how to use Q_PROPERTY.



  • @fcarney Thank you, that was the problem. I have implemented Q_PROPERTY following this conversation.

    I have inserted this line in Dispositivi.cpp

    Q_PROPERTY(AvailableDevicesModel *devicesModel READ availableDevicesFound NOTIFY availableDevicesChanged)
    

    Thanks again!



  • @ekkescorner Thanks for your answer! I will keep in mind your solution. It looks more easy than implementing all the stuff around QAbstractListModel.


  • Moderators

    @davidesalvetti great, that it's now working for you, but, just to make you aware of it, this

    static QObject *mySingleObject(QQmlEngine *engine, QJSEngine *scriptEngine)
    {
        Q_UNUSED(engine)
        Q_UNUSED(scriptEngine)
    
        MainBackend *backend = new MainBackend();
        return backend;
    }
    

    is insufficient for a singleton, as backend is not static
    also keep in mind, that in your case the QmlEngine will take ownership of the instance and if you ever want to access it from c++ the instance pointer may be invalid


  • Qt Champions 2016

    @davidesalvetti David, as soon as my customer app is ready and successfully working, I'll publish an Android 'Bluetooth SPP' example app, where I'm connecting to a Scanner and Printer via SPP - will let you know. (My current example App is BTLE only)



  • @ekkescorner Thank you! I would appreciate it!



  • @J-Hilk thanks a lot for the suggestion! I will keep in mind!


Log in to reply