I want to access the C++ classes of my App from QML by dot notation with the use of square brackets, for example:
myapp.object1.child_object2["key1"].grandchild_object1.property56
myapp.object2.child_object8[291045].grandchild_object4.property14
I would like to be able to bind these (C++) objects to QML properties or to use them in Javascript code. What would be a fast and proper way to do this?
For example, lets say that MyApp has a Users object, which is a QAbstractItemModel class and it has a QMap() of users. Each entry in this QMap has an email address as a key, and a User() object which contains user properties, like email, name, etc... So, the dot notation in this case would be:
myapp.users["johnsmith@domain.com"].name
myapp.users["johnsmith@domain.com"].password
Later, I would make it more complicated, adding a second level of hierarchy with a QMap() object for JohnSmith's tasks for example:
myapp.users["johnsmith@domain.com"].tasks[1].task_name
myapp.users["johnsmith@domain.com"].tasks[2].task_name
What mechanism should I use to expose my C++ objects in a HIERARCHICAL dot notation form ? I understand how PROPERTY macro works in Qt , but how to make it so that it could work for hierarchical dot notations ? Should I be using additional methods in my subclass of QAbstractItemModel class or should I overload the [] operator ? Is this possible (at all) to overload [] operator in QML engine? And, last question, if hierarchical dot notation in Qt is not possible at this time, would it be difficult to achieve by modifying the sources?
This is my source code, so far:
// File: main.cpp
#include
#include
#include "myapp.h"
#include "users.h"
#include "user.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType("com.myapp",1,0,"MyApp");
qmlRegisterType("com.myapp.users",1,0,"Users");
qmlRegisterType("com.myapp.user",1,0,"User");
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
MyApp class.
Header:
//File: myapp.h
#ifndef MYAPP_H
#define MYAPP_H
#include
#include
#include "users.h"
class MyApp : public QObject
{
Q_OBJECT
Q_PROPERTY(Users *users READ get_users WRITE set_users NOTIFY usersChanged);
private:
Users m_users;
public:
explicit MyApp(QObject *parent = 0);
Q_INVOKABLE Users *get_users();
void set_users(Users *data);
signals:
void usersChanged();
};
#endif // MYAPP_H
C source:
//File: myapp.cpp
#include
#include
#include "myapp.h"
#include "users.h"
#include "user.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType("com.myapp",1,0,"MyApp");
qmlRegisterType("com.myapp.users",1,0,"Users");
qmlRegisterType("com.myapp.user",1,0,"User");
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
Users file.
Header:
//File: users.h
#ifndef USERS_H
#define USERS_H
#include
#include
#include
#include "user.h"
class Users : public QAbstractItemModel
{
Q_OBJECT
enum UserRoles {
EmailRole = Qt::UserRole + 1,
NameRole,
PasswordRole
};
private:
QMap users_map;
public:
explicit Users(QAbstractItemModel *parent = 0);
Q_INVOKABLE QModelIndex index(int row, int column,const QModelIndex &parent = QModelIndex()) const;
Q_INVOKABLE QModelIndex parent(const QModelIndex &child) const;
Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const;
Q_INVOKABLE int columnCount(const QModelIndex &parent = QModelIndex()) const;
Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QHash roleNames() const;
signals:
public slots:
};
#endif // USERS_H
Source:
//File: users.cpp
#include
#include "users.h"
Users::Users(QAbstractItemModel *parent) : QAbstractItemModel(parent)
{
User *u;
u=new User();
u->set_email("johnsmith@domain.com");
u->set_name("John Smith");
u->set_password("123");
users_map.insert(u->get_email(),u);
u=new User();
u->set_email("juliepage@domain.com");
u->set_name("Julie Page");
u->set_password("321");
users_map.insert(u->get_email(),u);
}
QModelIndex Users::parent(const QModelIndex &child) const {
return QModelIndex();
}
QModelIndex Users::index(int row, int column,const QModelIndex &parent) const {
QList qlist;
qlist=users_map.keys();
if (row>=qlist.size()) return QModelIndex();
User *user=users_map[qlist.at(row)];
return createIndex(row,column,user);
}
int Users::rowCount(const QModelIndex &parent) const {
return users_map.size();
}
int Users::columnCount(const QModelIndex &parent) const {
return 1;
}
QVariant Users::data(const QModelIndex &index, int role) const {
int row_num;
row_num=index.row();
if (role==EmailRole) {
QList qlist;
qlist=users_map.keys();
if (row_num>=qlist.size()) return (QVariant());
return QVariant(qlist.at(row_num));
}
if (role==NameRole) {
QList qlist;
qlist=users_map.keys();
if (row_num>=qlist.size()) return (QVariant());
User *user=users_map.value(qlist.at(row_num));
return QVariant(user->get_name());
}
if (role==PasswordRole) {
QList qlist;
qlist=users_map.keys();
if (row_num>=qlist.size()) return (QVariant());
User *user=users_map[qlist.at(row_num)];
return QVariant(user->get_password());
}
if (role==Qt::DisplayRole) {
return(QVariant());
}
return (QVariant());
}
QHash Users::roleNames() const {
QHash roles;
roles[EmailRole] = "email";
roles[NameRole] = "name";
roles[PasswordRole] = "password";
return roles;
}
User file.
Header:
//File user.h
#ifndef USER_H
#define USER_H
#include
class User : public QObject
{
Q_OBJECT
Q_PROPERTY(QString email READ get_email WRITE set_email NOTIFY emailChanged);
Q_PROPERTY(QString name READ get_name WRITE set_name NOTIFY nameChanged);
Q_PROPERTY(QString password READ get_password WRITE set_password NOTIFY passwordChanged);
private:
QString email;
QString name;
QString password;
public:
explicit User(QObject *parent = 0);
const QString get_email();
void set_email(QString data);
const QString get_name();
void set_name(QString data);
const QString get_password();
void set_password(QString data);
signals:
void emailChanged();
void nameChanged();
void passwordChanged();
};
#endif // USER_H
Source:
//File: user.cpp
#include "user.h"
User::User(QObject *parent) : QObject(parent)
{
}
const QString User::get_email() {
return email;
}
void User::set_email(QString data) {
if (email!=data) {
email=data;
emit emailChanged();
}
}
const QString User::get_name() {
return name;
}
void User::set_name(QString data) {
if (name!=data) {
name=data;
emit nameChanged();
}
}
const QString User::get_password() {
return password;
}
void User::set_password(QString data) {
if (password!=data) {
password=data;
emit passwordChanged();
}
}
QML file.
//File: main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import com.myapp 1.0;
import com.myapp.users 1.0;
import com.myapp.user 1.0;
ApplicationWindow {
visible: true; width:640; height: 480;
MyApp {
id:myapp
}
ListView {
model: myapp.users
width: 300; height: 300
delegate: ItemDelegate {
Text {
text: model.email
}
}
}
Component.onCompleted: {
var users=myapp.users;
var user=users["johnsmith@domain.com"];
console.log("users object=" + users);
console.log("user object=" + user);
console.log("user's name="+user.name);
}
}
Project file.
//File: QML1.pro
QT += qml quick
CONFIG += c++11
SOURCES += main.cpp \
myapp.cpp \
users.cpp \
user.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Default rules for deployment.
include(deployment.pri)
HEADERS += \
myapp.h \
users.h \
user.h
And the debug output, when I run the program:
qml: users object=Users(0x15cd1b0)
qml: user object=undefined
qrc:/main.qml:28: TypeError: Cannot read property 'name' of undefined
As you can see, I can't pass beyond the [] square bracket point, the User object is not being retrieved by QML.
Answer
You can't use operators for your C++ objects from QML, you need to use functions if you want access from QML. If you already have an object with operators, you can write function wrappers which call the operators.
The dot syntax will work as long as QML knows of the objects, in order words, it requires to have meta information generated for them. Therefore objects must be QObject derived, or using Q_GADGET and exposed as Q_INVOKABLE functions or Q_PROPERTY.
In which case instead of:
myapp.object1.child_object2["key1"].grandchild_object1.property56
you will
// prop prop invokable prop prop
myapp.object1.child_object2.get("key1").grandchild_object1.property56
or if you implement sub-objects via accessor functions:
myapp.object1().child_object2().get("key1").grandchild_object1().property56
UPDATE:
Note that for a properly implemented QQmlListProperty you can use the [] operator, but only for index access. And you can't do it directly from the object, you have to do it from the object's list property, for example object.listProperty[index]. I noticed that even though you include QQmlListProperty in your code, it isn't really implemented.
So if you implement tasks as a QQmlListProperty of User then you could do something like this:
myapp.users.get("johnsmith@domain.com").tasks[1].task_name
Just make sure you don't go out of bounds. You also have tasks.length to avoid that.
I don't know exactly how this is implemented, but QQmlListProperty itself and associated types don't implement the [] operator. My guess is this is implemented someone on the QML engine level, and likely not as a part of a public API or directly available for use, which is quite typical for Qt internals.
No comments:
Post a Comment