【问题标题】:Qt: Accessing C++ objects from QML by dot notationQt:通过点符号从 QML 访问 C++ 对象
【发布时间】:2016-12-26 20:02:15
【问题描述】:

我想使用方括号通过点表示法从 QML 访问我的 App 的 C++ 类,例如:

myapp.object1.child_object2["key1"].grandchild_object1.property56
myapp.object2.child_object8[291045].grandchild_object4.property14

我希望能够将这些 (C++) 对象绑定到 QML 属性或在 Javascript 代码中使用它们。什么是快速而正确的方法来做到这一点?

例如,假设 MyApp 有一个 Users 对象,它是一个 QAbstractItemModel 类,它有一个用户的 QMap()。此 QMap 中的每个条目都有一个电子邮件地址作为键,以及一个包含用户属性(如电子邮件、姓名等)的 User() 对象......因此,在这种情况下,点符号将是:

myapp.users["johnsmith@domain.com"].name
myapp.users["johnsmith@domain.com"].password

稍后,我会让它变得更复杂,例如为 JohnSmith 的任务添加带有 QMap() 对象的第二级层次结构:

myapp.users["johnsmith@domain.com"].tasks[1].task_name
myapp.users["johnsmith@domain.com"].tasks[2].task_name

我应该使用什么机制以 HIERARCHICAL 点符号形式公开我的 C++ 对象?我了解 PROPERTY 宏在 Qt 中的工作原理,但如何使其适用于分层点符号?我应该在 QAbstractItemModel 类的子类中使用其他方法还是应该重载 [] 运算符?这是否可能(完全)在 QML 引擎中重载 [] 运算符?而且,最后一个问题,如果此时 Qt 中的分层点表示法是不可能的,是否很难通过修改源代码来实现?

这是我的源代码,到目前为止:

// File: main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#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<MyApp>("com.myapp",1,0,"MyApp");
    qmlRegisterType<Users>("com.myapp.users",1,0,"Users");
    qmlRegisterType<User>("com.myapp.user",1,0,"User");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));

    return app.exec();
}

MyApp 类。 标题:

//File: myapp.h


#ifndef MYAPP_H
#define MYAPP_H

#include <QObject>
#include <QQmlListProperty>
#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源码:

//File: myapp.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#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<MyApp>("com.myapp",1,0,"MyApp");
    qmlRegisterType<Users>("com.myapp.users",1,0,"Users");
    qmlRegisterType<User>("com.myapp.user",1,0,"User");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));

    return app.exec();
}

用户文件。 标题:

//File: users.h

#ifndef USERS_H
#define USERS_H

#include <QObject>
#include <QAbstractItemModel>
#include <QMap>
#include "user.h"

class Users : public QAbstractItemModel
{
    Q_OBJECT
    enum UserRoles {
        EmailRole = Qt::UserRole + 1,
        NameRole,
        PasswordRole
    };
private:
    QMap<QString,User*>         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<int, QByteArray> roleNames() const;
signals:

public slots:
};    
#endif // USERS_H

来源:

//File: users.cpp
#include <QDebug>
#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 <QString> 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 <QString> qlist;
        qlist=users_map.keys();
        if (row_num>=qlist.size()) return (QVariant());
        return QVariant(qlist.at(row_num));
    }
    if (role==NameRole) {
        QList <QString> 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 <QString> 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<int, QByteArray> Users::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[EmailRole] = "email";
    roles[NameRole] = "name";
    roles[PasswordRole] = "password";
    return roles;
}

用户文件。 标题:

//File user.h
#ifndef USER_H
#define USER_H

#include <QObject>

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

来源:

//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: 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);
    }
}

项目文件。 //文件: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

还有调试输出,当我运行程序时:

qml: users object=Users(0x15cd1b0)
qml: user object=undefined
qrc:/main.qml:28: TypeError: Cannot read property 'name' of undefined

如您所见,我无法越过 [] 方括号,QML 未检索到 User 对象。

【问题讨论】:

  • 文字的圣墙!!!当提出问题时,尽量用最少的代码来呈现你的问题,这只是一个重现问题的简单例子,而不是你的整个生产代码,对那些试图提供帮助的人有怜悯之心。 NO - 你不能在 QML 中重载运算符,在 JS 中没有运算符重载,如果你想从 QML 访问功能,请使用插槽或可调用函数。
  • @ddriver,呵呵,这不是生产代码,它是一个示例,生产代码>25k 行。太糟糕了,重载 [] 运算符将是一个不错的功能。也许 var somevar=object.method().submethod().subsubmethod() 会起作用?
  • 是的,只要所有对象都是QObject 派生的或通过Q_GADGET 生成的元数据,并且成员是可调用的函数或属性。 QML 必须知道这些东西,而这些知识来自元信息的生成。顺便说一句,您代码中的大部分内容与主题完全无关。这对任何人都没有帮助,因此不是一个好例子。
  • @ddriver,但我必须复制 Users.cpp 和 User.cpp,否则您将无法理解我的对象是如何相互链接的。并且刚刚添加了其他文件以使其成为 100% 可编译的示例。
  • 您可以轻松地用 20 行代码重现该问题。此外,在提供此类琐碎示例时,最好在现场实现函数,而不是拆分声明和定义,这样人们就可以一目了然地看到正在发生的事情,而不是四处滚动。

标签: c++ qt qml qt5


【解决方案1】:

您不能对来自 QML 的 C++ 对象使用运算符,如果您想从 QML 访问,则需要使用函数。如果您已经有一个带有操作符的对象,您可以编写调用操作符的函数包装器。

只要 QML 知道对象,点语法就可以工作,换句话说,它需要为它们生成元信息。因此对象必须是QObject 派生的,或者使用Q_GADGET 并公开为Q_INVOKABLE 函数或Q_PROPERTY

在这种情况下,而不是:

myapp.object1.child_object2["key1"].grandchild_object1.property56

你会

//    prop    prop          invokable   prop               prop
myapp.object1.child_object2.get("key1").grandchild_object1.property56

或者如果您通过访问器函数实现子对象:

myapp.object1().child_object2().get("key1").grandchild_object1().property56

更新:

请注意,对于正确实现的QQmlListProperty,您可以使用[] 运算符,但仅用于索引访问。而且您不能直接从对象中执行此操作,您必须从对象的列表属性中执行此操作,例如object.listProperty[index]。我注意到即使您在代码中包含QQmlListProperty,它也没有真正实现。

因此,如果您将任务实现为UserQQmlListProperty,那么您可以执行以下操作:

myapp.users.get("johnsmith@domain.com").tasks[1].task_name

只要确保你没有越界。您还可以使用tasks.length 来避免这种情况。

我不确切知道这是如何实现的,但QQmlListProperty 本身和相关类型没有实现[] 运算符。我的猜测是这是在 QML 引擎级别上实现的,可能不是作为公共 API 的一部分或直接可用,这对于 Qt 内部来说是非常典型的。

【讨论】:

  • 谢谢!我认为通过访问器函数,如果访问器函数是为 QML 设计的,它可以看起来更好,更具可读性
  • 属性可以有通知,如果属性发生变化,任何绑定到它的代码都会自动重新计算。因此属性也有点冗长。如果您不需要可调用的函数,那么您可以这样做。但是,您可以在没有通知的情况下使用属性,甚至不需要实现访问器函数,您可以将MEMBER 属性用于聚合子对象,这非常快速和干净。基本上是Q_PROPERTY(Type subObject MEMBER someSubObject)。然后您可以节省 () 并实现实际的访问器。
  • 如果我实现 QQmlListProperty 它将是 myapp.users["johnsmith@domain.com"].tasks[1].task_name ,对吗?没有“get()”方法,对吗? (因为您仍在使用示例中的函数)
  • 不,您仍然需要get() 方法来使用字符串键。 [] 仅适用于整数索引。
  • 不,没关系。但除非你真的需要,否则你不应该使用它。并且只有在使用声明性 QML 代码“填充”列表时才需要。如果你所有的东西都来自 C++,你就不需要它了。
猜你喜欢
  • 1970-01-01
  • 2021-03-22
  • 2018-05-17
  • 2019-04-13
  • 2016-10-31
  • 2012-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多