Solved 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!
-
@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) -
@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.
-
@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.
-
@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 -
@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!