Read C++ QVector of structs in QML - c++

In my C++ class I have
struct trackPoint {
QString lat;
QString lon;
QString elevation;
};
QVector<trackPoint> trackPoints;
In QML I want to access this as a multi-dimensional array of lon,lat pairs
[[0,1],[1,1],[2,1]]
Is this possible using the Q_Property mechanism? As I am pretty sure that structs cannot be exposed to QML?
I've tied:-
Q_PROPERTY(QVector<trackPoint> trackPoints READ gpx)
With a method:-
QVector<trackPoint> GPXFileIO::gpx() const {
return trackPoints;
}
But this gives me the error:-
QMetaProperty::read: Unable to handle unregistered datatype 'QVector<trackPoint>' for property 'GPXFileIO::trackPoints'

A simple way to expose a struct to QML is using Q_GADGET with Q_PROPERTY so we can get each element of the structure, they will not be part of an array. On the other hand QVector is supporting a number of elements with QString, int, QUrl, etc. but not for new types, in which case QVariantList should be used.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QVector>
struct TrackPoint {
Q_GADGET
Q_PROPERTY(qreal lat MEMBER lat)
Q_PROPERTY(qreal lon MEMBER lon)
Q_PROPERTY(qreal elevation MEMBER elevation)
public:
qreal lat;
qreal lon;
qreal elevation;
};
class TrackClass: public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList trackpoints READ gpx)
public:
TrackClass(QObject *parent=nullptr):QObject(parent){
trackPoints << TrackPoint{10, 10, 10} << TrackPoint{11, 11, 11};
}
QVariantList gpx() const{
QVariantList l;
for(const TrackPoint & p: trackPoints){
l << QVariant::fromValue(p);
}
return l;
}
private:
QVector<TrackPoint> trackPoints;
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
TrackClass track;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("track", &track);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component.onCompleted: {
for( var i in track.trackpoints){
var p = track.trackpoints[i];
console.log("lat: ", p.lat, "lon: ", p.lon, "elevation: ", p.elevation)
}
}
}
Output:
qml: lat: 10 lon: 10 elevation: 10
qml: lat: 11 lon: 11 elevation: 11

Without adding the complexity of gadgets, I find pretty straight forward the usage of QVariantList of QVariantMap, as Qvariant.
This is how I do it:
Q_PROPERTY(QVariant trackpoints READ gpx NOTIFY gpxChanged)
QVariant TrackClass::gpx() const
{
QVariantList itemsList;
for(const TrackPoint &p : trackPoints)
{
QVariantMap itemMap;
itemMap.insert("lat", p.lat);
itemMap.insert("lon", p.lon);
itemMap.insert("elevation", p.elevation);
itemsList.append(itemMap);
}
return QVariant::fromValue(itemsList);
}
Then in QML you can use trackpoints as model and access item fields by name.
It is a good practice to also add a NOTIFY signal, to be called when your QVector changes.

Related

Unable to populate ComboBox model with QVariantList

From QML:
AudioInfoCpp is the C++ class's object. Aim to to get data from C++ and fill it in the model of the combobox.
property ListModel comboModel: cbItems
AudioInfoCpp
{
id: audioInfoCpp
Component.onCompleted:
{
fetchInputDevices()
comboModel.append(inputDevices1)
console.log(inputDevices1)
}
}
ComboBox
{
id: comboBoxM;
width: 100
model: audioInfoCpp.inputDevices1;
textRole: "langDescription";
}
From .h
#ifndef AUDIOINFO_H
#define AUDIOINFO_H
#include <QAudioDeviceInfo>
#include <QList>
#include <QVariantList>
class AudioInfo : public QObject
{
Q_OBJECT
protected:
QAudioDeviceInfo objQAudioDeviceInfo;
public:
AudioInfo();
Q_PROPERTY(QVariantList inputDevices1 READ inputDevices1 WRITE setInputDevices1 NOTIFY inputDevices1Changed)
Q_INVOKABLE void fetchInputDevices();
QVariantList inputDevices1() const
{
return m_inputDevices1;
}
public slots:
void setInputDevices1(QVariantList inputDevices1)
{
if (m_inputDevices1 == inputDevices1)
return;
m_inputDevices1 = inputDevices1;
emit inputDevices1Changed(m_inputDevices1);
}
signals:
void inputDevices1Changed(QVariantList inputDevices1);
private:
QVariantList m_inputDevices1;
};
#endif // AUDIOINFO_H
.cpp :
void AudioInfo::fetchInputDevices()
{
QList<QAudioDeviceInfo> devices1 = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
if (devices1.empty())
{
qDebug() << "No audio input devices";
}
QVariantList tt;
for (const QAudioDeviceInfo& device : devices1)
{
tt.append(device.deviceName());
}
setInputDevices1( tt );
}
In QML as well as CPP I can see the data printed from the list but I am not able to populate the combobox. There are no errors.
Please guide.
The problem comes from this line:
textRole: "langDescription"
That means it will look through each element in your model for the langDescription field. But you are populating your QVariantList with QStrings:
QVariantList tt;
for (const QAudioDeviceInfo& device : devices1)
{
tt.append(device.deviceName());
}
QStrings don't have a langDescription field, so nothing gets displayed.
To solve this try completely removing the textRole line and allow QML to automatically figure out how to display the names correctly.

How to create chart in QML with series data exposed from c++

I am filling QLineSeries data on c++ side and QML chart is supposed to consume them (and update as they change). Data producer is connected to newData which are added to the series data, and that should trigger repaint of the chart.
Previously, the LineSeries were manipulated in QML but now I don't know how to make the c++ QLineSeries instances accessible to QML.
// ...
#include<QtCharts/QLineSeries>
using namespace QtCharts;
/* class holding all data to be displayed as properties */
class UiData: public QObject{
Q_OBJECT
Q_PROPERTY(QLineSeries *xy READ getXy NOTIFY xyChanged);
QLineSeries* getXy(){return &xy; }
signals:
void xyChanged();
public slots:
void newData(float x, float y){
xy.append(x,y);
emit xyChanged();
}
private:
QLineSeries xy;
}
int main(int argc, char* argv[]){
QApplication app(argc,argv);
QQmlApplicationEngine engine;
UiData uiData;
/* make the instance accessiblt from QML */
engine.rootContext()->setContextProperty("uiData",&uiData);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// ...
return qApp->exec();
}
main.qml:
ChartView{
ValueAxis{
id: _axisX;
}
ValueAxis{
id: _axisY;
}
/*
BEFORE:
data in QML: easy, plus had JS slot calling _xySeries.append(...)
*/
LineSeries{
id: _xySeries;
axisX: _axisX;
axisY: _axisY;
}
/*
NOW:
how to use an already existing LineSeries instance (uiData.xy),
and only change a few attributes, like axisX and axisY?
Something like this:
LineSeries(uiData.xy) {
axisX: _axisX;
axisY: _axisY;
}
or like this:
component.onCompleted:{
// where is something like ChartView.addSeries?
// (there is ChartView.removeSeries already)
uiData.xy.axisX=_axisX;
uiData.xy.axisY=_axisY;
addSeries(uiData.xy);
}
*/
};
Is there a good solution to this?
In an extreme case, I can also create the whole ChartView in c++ (but then again, how to insert it into the QML?).
It can be said that LineSeries is a QLineSeries + other functionalities, so ChartView uses those other functionalities.
What you must do is access the LineSeries in UiData, for this you must use the memory position.
class UiData: public QObject{
Q_OBJECT
Q_PROPERTY(QLineSeries* xy READ xy WRITE setXy NOTIFY xyChanged)
public:
...
QLineSeries *xy() const
{
return mXy;
}
void setXy(QLineSeries *xy)
{
if(mXy == xy) return;
if(mXy)
if(mXy->parent() == this) // is owner
delete mXy;
mXy = xy;
emit xyChanged();
}
public slots:
void newData(qreal x, qreal y){
if(mXy){
mXy->append(x, y);
}
}
...
signals:
void xyChanged();
...
private:
QLineSeries *mXy;
};
*.qml
ChartView{
ValueAxis{
id: _axisX;
}
ValueAxis{
id: _axisY;
}
LineSeries{
id: _xySeries;
axisX: _axisX;
axisY: _axisY;
}
}
Component.onCompleted: uiData.xy = _xySeries // <----
As you can see, a QLineSeries is not being created in C ++, but the pointer serves only to reference LineSeries.
The complete example can be found in the following link.

QML: Refresh issue while receivinig C++ signals passed from C++

Hi i have bee playing recently with Qt for mobile development. I started simple using all the code in C++ (given that I have more experience with it) and now I'm starting to use QML. In my app I have a QQuickWidget in the ui where I display a map. The goal is simple to center the map every time the phone emits the coordinates.
This is how I set up my QML
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
and this is my QML file
Rectangle {
width: 300
height: 300
visible: true
Plugin {
id: osmPlugin
name: "esri"
// specify plugin parameters if necessary
// PluginParameter {
// name:
// value:
// }
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
center: QtPositioning.coordinate(51.0, -114.0)
zoomLevel: 10
activeMapType: map.supportedMapTypes[1] // This selects the type of map to display
}
I declared a class to handle the sending of the coordinates to the QML and setup a connection on the QML for receiving the signal
#ifndef COORDUPDATE_H
#define COORDUPDATE_H
#include <QObject>
#include <QVariant>
class coordUpdate : public QObject
{
Q_OBJECT
public:
explicit coordUpdate(QObject *parent = nullptr);
// Q_INVOKABLE void sendupd(double xcoord, double ycoord);
private:
signals:
void sendCoord(QVariant xcoord,QVariant ycoord);
private slots:
public slots:
void sendupd(double xcoord, double ycoord);
};
#endif // COORDUPDATE_H
#include "coordupdate.h"
#include <QDebug>
coordUpdate::coordUpdate(QObject *parent) :
QObject(parent)
{
}
void coordUpdate::sendupd(double xcoord,double ycoord){
emit sendCoord(xcoord,ycoord);
qDebug() << "signal emitted";
}
The problem I'm having is that if I call
void MainWindow::setGPSLocation(QGeoPositionInfo geoPositionInfo)
{
statusBar()->showMessage("Updating position...",1000);
QString text="Location=unknown";
if (geoPositionInfo.isValid())
{
// get the current location coordinates
QGeoCoordinate geoCoordinate = geoPositionInfo.coordinate();
// transform coordinates to lat/lon
qreal latitude = geoCoordinate.latitude();
qreal longitude = geoCoordinate.longitude();
qreal altitude = geoCoordinate.altitude();
double lon = longitude;
double lat = latitude;
//on position updated we also need to record the data
coordUpdate upd;
ui->quickWidget->rootContext()->setContextProperty("updcoord",&upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
upd.sendupd(longitude,latitude);
qDebug() << "reading coordinates" << longitude << latitude;
}
}
Then the coordinates get updated but the whole thing gets refreshed. Is there a better way to update the information sent to the QML file? I know I would better developing in QML exclusively but I very interested in the use of QQuickWiget for the time being.
I'm not asking how to connect a signal from C++ to QML. The code does that. What I was asking was how to properly update the map with the information passed. I already read the documentation. Unfortunately not much of the documentation focus on QQuickWidgets but rather have a main class instantiated with QQmlEngine.
As described here http://doc.qt.io/qt-5/qtqml-cppintegration-overview.html, you have basically three ways to integrate C++ with QML code :
Expose your C++ class to QML ( properties, signals, slots..) and register it as an instanciable QML type
Same as 1 but register it as a singleton type so it is instantiated by the QML engine, not explicitly in the QML code
Same as 1 but make it available to QML via context properties for example, in this case, you create the instance of your class on the C++ side of your application
Solved it by moving the Connections inside the Map and redefining in the mainwindow.cpp and mainwindow.h the way the QML is instantiated. That way the map is refreshed properly every time the QML captures the signal.
Added to mainwindow.cpp
private:
coordUpdate *upd;
Changed
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->startU->setDisabled(true);
setupLogging();
setupGPS();
coordUpdate upd;
ui->quickWidget->rootContext()->setContextProperty("updcoord",upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
}
to
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->startU->setDisabled(true);
setupLogging();
setupGPS();
upd = new coordUpdate;
ui->quickWidget->rootContext()->setContextProperty("updcoord",upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
}
and in the QML
Rectangle {
width: 300
height: 300
visible: true
Plugin {
id: osmPlugin
name: "esri"
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
// center: QtPositioning.coordinate(y, x)
zoomLevel: 15
activeMapType: map.supportedMapTypes[1] // This selects thetype of mapa to display
MapCircle {
id:circle
center: QtPositioning.coordinate(y, x)
radius: 50
color: 'red'
}
// Create connections with c++
Connections // Define actions for custom slots
{
id:cppConnection
target: updcoord
onSendCoord: {
// To access signal parameter, name the parameter
console.log("signal received",ycoord,xcoord)
circle.center = QtPositioning.coordinate(ycoord,xcoord)
}
}
}
}

Qt ListView doesn't show C++ model content

I'm creating a QList<> in C++ using a QML ListView to display it. The application runs without errors but the ListView stubbornly remains empty.
The QML will show a rectangle for the presence of each list item.
I checked the UI code by creating a list in QML.
It displays correctly for the QML created list.
Here's my QML:
import Processes 1.0
...
ListView {
id: qInterfaceList
height: parent.height;
width: parent.width;
model: myModel
orientation: ListView.Vertical
delegate:
Rectangle {
height: 30;
width: 120;
border.color: "red"
border.width: 3
}
The C++ code that creates and registers the list object:
// Register C++ classes as a QML type named Processes (version 1.0)
qmlRegisterType<Process>("Processes", 1, 0, "Process");
QQmlApplicationEngine engine;
// read the configuration file
Config conf;
if ( conf.read() )
{
QQmlContext* ctxt = engine.rootContext();
if ( ctxt )
{
qDebug()
<< "--- conf.Interfaces: "
<< conf.Interfaces.length()
;
ConfigInterface c;
QVariant v = QVariant::fromValue( conf.Interfaces );
qDebug()
<< "--- ConfigInterface: "
<< v
<< "--- typeName: "
<< v.typeName()
;
ctxt->setContextProperty("myModel", QVariant::fromValue( conf.Interfaces ));
}
}
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
To debug I output information about list from C++ and QML:
In C++ the count of list items is correct.
In C++ the conversion to QVariant is working.
In QML it sees the defined list.
The debugging output:
Debugging starts
--- conf.Interfaces: 65
--- ConfigInterface: QVariant(QList<ConfigInterface*>, ) --- typeName: QList<ConfigInterface*>
qml: myModel: QVariant(QList<ConfigInterface*>)
Debugging has finished
Any ideas what's wrong or how to debug this?
Thanks
EDIT: Here's the class being used as a list item
Class declaration:
class ConfigInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(QString sql READ getTag WRITE setTag NOTIFY tagChanged)
Q_PROPERTY(QString description READ getDescription WRITE setDescription NOTIFY descriptionChanged)
public:
/*explicit*/ ConfigInterface();
/*explicit*/ ConfigInterface(QObject *parent);
~ConfigInterface();
// Copy constructor needed because these are copied when added to a QList
ConfigInterface(const ConfigInterface &p2) {_tag = p2._tag; _description = p2._description; }
QString getDescription() const;
void setDescription(QString&);
QString getTag() const;
void setTag(QString&);
signals:
void tagChanged(QString);
void descriptionChanged(QString);
public:
QString _tag;
QString _description;
QString QueryTemplate;
QString ConnectString;
QString MinimumId;
};
Q_DECLARE_METATYPE(ConfigInterface*)
C++ code:
ConfigInterface::ConfigInterface()
: QObject( nullptr )
{
}
ConfigInterface::ConfigInterface(QObject* parent)
: QObject(parent)
{
}
ConfigInterface::~ConfigInterface()
{
}
QString ConfigInterface::getTag() const
{
return _tag;
}
void ConfigInterface::setTag(QString& str)
{
_tag = str;
emit tagChanged(_tag);
}
The main problem is caused because it has a list of ConfigInterface *, according to the examples provided in the documentation should be a list of QObject *:
class Config{
[...]
public:
QList<QObject *> Interfaces;
[...]
};
In addition to this you should get the following warning:
/..../configinterface.h:17: warning: base class ‘class QObject’ should be explicitly initialized in the copy constructor [-Wextra]
ConfigInterface(const ConfigInterface &p2) {_tag = p2._tag; _description = p2._description; }
^~~~~~~~~~~~~~~
This is caused because QObject and its derived classes must not have a copy constructor or assignment operator, For more information read the following:
http://doc.qt.io/qt-5/qobject.html#no-copy-constructor-or-assignment-operator
Another improvement is that both constructors can be united in only one, in the end their class could have the following structure:
configinterface.h
#ifndef CONFIGINTERFACE_H
#define CONFIGINTERFACE_H
#include <QObject>
class ConfigInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(QString sql READ getTag WRITE setTag NOTIFY tagChanged)
Q_PROPERTY(QString description READ getDescription WRITE setDescription NOTIFY descriptionChanged)
public:
ConfigInterface(QObject *parent=Q_NULLPTR);
~ConfigInterface();
QString getTag() const;
void setTag(const QString &tag);
QString getDescription() const;
void setDescription(const QString &description);
signals:
void tagChanged(QString);
void descriptionChanged(QString);
private:
QString _tag;
QString _description;
QString QueryTemplate;
QString ConnectString;
QString MinimumId;
};
#endif // CONFIGINTERFACE_H
configinterface.cpp
#include "configinterface.h"
ConfigInterface::ConfigInterface(QObject* parent)
: QObject(parent)
{
}
ConfigInterface::~ConfigInterface()
{
}
QString ConfigInterface::getDescription() const
{
return _description;
}
void ConfigInterface::setDescription(const QString &description)
{
if(_description == description)
return;
emit descriptionChanged(description);
_description = description;
}
QString ConfigInterface::getTag() const
{
return _tag;
}
void ConfigInterface::setTag(const QString &tag)
{
if(tag == _tag)
return;
emit tagChanged(tag);
_tag = tag;
}

QList<QList<QString>> passed into QML

I'm trying to pass a 2d QList as a Q_PROPERTY into QML, however, inside QML and i am unable to actually access any of the information.
some code:
c++:
the q_property get populated by a q_invokable function in the constructor:
void Class::createNewGameArray(){
QList<QList<QString>> testArray;
for( int i = 0; i < _intervals.size(); ++i) {
QList<QString> innerArray;
testArray.append(innerArray);
testArray[i].append(_intervals[i]);
testArray[i].append("Audio");
}
for( int i = 0; i < _intervals.size(); ++i) {
QList<QString> innerArray;
testArray.append(innerArray);
testArray[i+12].append(_intervals[i]);
testArray[i+12].append("Text");
}
std::random_shuffle(testArray.begin(),testArray.end());
Class::setGameArray(testArray);
emit gameArrayChanged(_newGameArray);
which returns this:
(("M7", "Text"), ("M3", "Text"), ("m3", "Text"), ("M6", "Audio"), ("TT", "Audio"), ("P4", "Text"), ("m7", "Audio"), ("m2", "Text"), ("m6", "Audio"), ("m6", "Text"), ("M7", "Audio"), ("P5", "Text"), ("P4", "Audio"), ("m2", "Audio"), ("M2", "Audio"), ("M3", "Audio"), ("P5", "Audio"), ("m3", "Audio"), ("M6", "Text"), ("TT", "Text"), ("m7", "Text"), ("Oct", "Audio"), ("Oct", "Text"), ("M2", "Text"))
exactly what i want.
i set the rootContext like so in main.cpp:
Class object;
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("object", &object);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
however, inside qml i only get
qml: QVariant(QList<QList<QString> >)
and am unable to actually do anything with it.
My goal, ideally, would be to be able to access the 2d qlist from qml in this manner:
object.gameArray[0][1]
// return "Text"
I'm able to do this with regular QLists (without the 2d). Any help would be greatly appreciated!
QML does not inherently understand QLists, so in general it is not possible to pass in a QList of any type T and have QML able to access the items inside the list.
However, the QML engine does have built in support for a few specific types of QList:
QList<QObject *>
QList<QVariant>
QStringList - (not QList<QString>!!!)
Therefore if you can construct your list of lists using any combination of the 3 types above, then you can have a working solution. In your use case I would suggest the following construction:
QList<QVariant(QStringList)>
A final note before we try it... Just because this will work, it does not necessarily mean that it is a good idea. The QList contents are copied to Javascript arrays at runtime, and therefore any minor updates to any of the lists from the C++ will cause the entire list to be reconstructed as a new Javascript array, which could be expensive.
Now, let's try it...
myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QStringList>
#include <QVariant>
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QVariant> variantList READ variantList NOTIFY variantListChanged)
public:
explicit MyClass(QObject *parent = nullptr) : QObject(parent),
m_variantList({
QStringList({ "apple", "banana", "coconut" }),
QStringList({ "alice", "bob", "charlie" }),
QStringList({ "alpha", "beta", "gamma" })
}) { }
QList<QVariant> variantList() const { return m_variantList; }
signals:
void variantListChanged();
public slots:
private:
QList<QVariant> m_variantList;
};
#endif // MYCLASS_H
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
Column {
id: column
// will add the strings here from the handler below
}
Component.onCompleted: {
console.log("variantList length %1".arg(myClass.variantList.length))
for (var i = 0; i < myClass.variantList.length; i++) {
console.log("stringList %1 length %2".arg(i).arg(myClass.variantList[i].length))
for (var j = 0; j < myClass.variantList[i].length; j++) {
// print strings to the console
console.log("variantList i(%1), j(%2) = %3".arg(i).arg(j).arg(myClass.variantList[i][j]))
// add the strings to a visual list so we can see them in the user interface
Qt.createQmlObject('import QtQuick 2.7; Text { text: "i(%1), j(%2) = %3" }'.arg(i).arg(j).arg(myClass.variantList[i][j]), column)
}
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "myclass.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
MyClass myClass;
engine.rootContext()->setContextProperty("myClass", &myClass);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
Runtime output
qml: variantList length 3
qml: stringList 0 length 3
qml: variantList i(0), j(0) = apple
qml: variantList i(0), j(1) = banana
qml: variantList i(0), j(2) = coconut
qml: stringList 1 length 3
qml: variantList i(1), j(0) = alice
qml: variantList i(1), j(1) = bob
qml: variantList i(1), j(2) = charlie
qml: stringList 2 length 3
qml: variantList i(2), j(0) = alpha
qml: variantList i(2), j(1) = beta
qml: variantList i(2), j(2) = gamma
... and it works :)
Automatic conversions would only work for several specific types of containers, and that's it. Just because conversion A works, and conversion B works, doesn't mean that conversion A will work too.
You can pretty much forget about using the [] operator in all the cases where automatic conversion doesn't work.
A variant list of variant lists however might just work. I haven't tested it myself but there is a slim hope. However, you will have to manually do the conversion before you pass that stuff to QML.
An approach that will most definitely work is to create accessor functions, like for example QString Class::get(int row, int col) or you can have separate accessors to select a row, and then pass that result to another function to select a column to give you the string.