QML学习之路(4)——QML与C++交互

在QML编程中,C++往往用于编写后台逻辑,而QML用于进行界面端编写,因此利用C++与QML进行交互就成为了重点。由于C++与QML交互具有灵活的方式,本文将针对其中一种方式进行讲解。该方式的基本思想为:

  1. 处理用户交互事件的逻辑在QML中进行
  2. 接收到用户交互事件后,由QML发送信号至APP
  3. 在负责业务逻辑的C++文件中找到QML中对应的QObject,并将其信号与指定槽函数绑定

在这个过程中,QML可以被视作一个黑盒子,通过属性、信号和函数与外界进行交互。

在QML中处理用户交互事件

假如我有一个MainMenu.qml,用于实现应用程序的菜单栏,其部分代码如下所示:

1
2
3
4
5
6
7
// in MainMenu.qml
Menu {
title: "&File"
MenuItem {
action: newAction
}
}

里面包含了一个MenuItem,他有一个指定的action,action中代码如下:

1
2
3
4
5
6
7
8
9
Action {
id: newAction
objectName: "newAction"
text: "New Project"
signal clicked()
onTriggered: {
newAction.clicked()
}
}

可以看到,当触发Action之后,qml会发送一条signal,内容为clicked(),此时我们可以在C++中接收这个信号,并进行相应处理。

在负责业务逻辑的C++文件中找到QML中对应的QObject

假设我们现在有了一个engine,并加载了对应的qml文件:

1
2
3
//in main.cpp
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/MainMenu.qml")));

我们现在要在qml中找到能够发送signal的对应的action,其代码如下所示:

1
2
QObject *root = engine.rootObjects().first();
QObject *newAction = root->findChild<QObject* >("newAction"); //这个"newAction"就是qml中id为newAction的Action的objectName

将信号与槽函数绑定

在接收到信号后,我们所要做的就是将信号与指定槽函数进行绑定即可

1
QObject::connect(newAction,SIGNAL(clicked()),&w,SLOT(newSlot()));   //clicked即为qml中的信号cllicked()

经过一系列操作,即可实现由QML到C++进行通信的过程

在C++中创建QML对象

有时我们希望能够从C++中向QML发送数据,这种情况需要我们在C++中创建QML可以识别的对象,本节将对C++中创建QML对象的方法进行总结。

继承QQuickItem类

当我们需要创建一个QML对象时,我们需要继承QQuickItem类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Squircle : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged)
QML_ELEMENT //声明该类为QML下的一个element

public:
Squircle();

qreal t() const { return m_t; }
void setT(qreal t);

signals:
void tChanged();

public slots:
void sync();
void cleanup();

private slots:
void handleWindowChanged(QQuickWindow *win);

private:
void releaseResources() override;

qreal m_t;
SquircleRenderer *m_renderer;
};

属性系统

一个类的属性就是一个类的成员变量,由于QML与C++语法不互通,我们需要一个属性声明系统,让QML能够辨识C++中的成员及成员函数,这个属性系统能够声明C++中的一个成员变量,并提供访问的读/写操作

1
Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged)

在上面这行语句中,我们声明了一个类型为qreal的t变量,同时赋予了读方法t和写方法setT,NOTIFY为给属性值设置相应信号。

Q_PROPERTY常用格式

此处列举一些常用格式,更常见的请参考参考文献3

指定读取属性的函数
1
2
3
4
5
6
7
Class Widget : public QObject
{
Q_PROPERTY(bool focus READ hasFocus)
Q_OBJECT
public:
bool hasFocus() const;
}
修改属性的函数
1
2
3
4
5
6
7
8
Class Widget : public QObject
{
Q_PROPERTY(bool focus WRITE setFocus)
Q_OBJECT
public:
bool hasFocus() const;
void setFocus(bool on);
}

我们限定set函数无返回值且只能有一个输入参数

导出成员变量为属性值

上面两个读/写的属性值都不是类中的成员变量,是凭空声明出来的一个属性值。要想将类中已有的成员变量设置为属性值,需要用 MEMBER 关键字。这样focus 这个属性值就变的可读可写了。要读可以用类自己的 hasFocus() 函数,要写用自带的setFocus() 修改 m_focus 变量就可以了,属性值会自动更新。

1
2
3
4
5
6
7
8
9
10
Class Widget : public QObject
{
Q_PROPERTY(bool focus MEMBER m_focus)
Q_OBJECT
public:
bool hasFocus() const;
void setFocus(bool on);
private:
bool m_focus;
}

虽然READWRITEMEMBER这三个关键字都可以赋予属性值可读可写的特性,但是 READWRITEMEMBER不能同时使用,赋予可读可写特性一次就够了,不能赋予两次。就好像一个对象不能被析构两次一样。

给属性设置关联信号
1
2
3
4
5
6
7
8
9
10
11
12
Class Widget : public QObject
{
Q_PROPERTY(bool focus MEMBER m_focus NOTIFY focusChanged)
Q_OBJECT
public:
bool hasFocus() const;
void setFocus(bool on);
signals:
void focusChanged();
private:
bool m_focus;
}

这样,在m_focus发生变化时,可以自动发送信号focusChanged

参考文献

0%