Qt6 ++开发
1. 简介
- rcc: Resource Compiler (资源编译器)
- MOC:Meta-Object Compiler (元对象编译器)
- Qt Quick:是用户界面技术的总称,是一个基于QML的框架,是以下几种技术的集合:
- QML:用户界面的标记语言,用于描述对象如何相互关联的声明式语言
- JavaScript:动态脚本语言
- Qt C++:高度可移植的增强型C++库
- 信号来源:
- 元素事件
- QML中定义
- C++中定义
- 信号与槽
- 信号(Signal):就是在特定情况下被发射的事件,信号只需声明,无需定义
- 槽(Slot):就是对信号响应的函数。槽就是一个函数,与一般的 C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行
- 槽可以是任何成员函数、普通全局函数、静态函数
- 槽函数和信号的参数和返回值要一致
- GUI 程序设计的主要内容就是对界面上各组件的信号的响应,只需要知道什么情况下发射哪些信号,合理地去响应和处理这些信号就可以了
- 每次信号发送时都会触发调⽤槽函数
- 通过信号与槽的机制实现了松耦合的对象通信,使界面各个组件的交互操作变得更加直观和简单
- C++中声明方式
class MainWindow : public QMainWindow
{
Q_OBJECT
signals:
/* 声明一个信号,只需声明,无需定义 */
void pushButtonTextChanged();
public slots:
/* 声明一个槽函数 */
void changeButtonText();
/* 声明按钮点击的槽函数 */
void pushButtonClicked();
}
/* 连接信号与槽 */
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
connect(this, SIGNAL(pushButtonTextChanged()), this, SLOT(changeButtonText()));
- QML的优点:
- 数据 与可视化分离
- model-view(模型-视图):model代表数据,view代表可视化
- 后端开发者由功能驱动,前段开发者由⽤户场景驱动
- QML/C++/JS相互调用
- QML可以调⽤暴露的C++对象实例
- JavaScript可调⽤C++函数
- 内省:意味着Qt对象知道它的类名,它与其它类的关系,以及它的⽅法和属性。
- 内存管理:意味着每个Qt对象都可以成为是其它⼦对象的⽗对象。⽗对象拥有⼦对象,当⽗对象销毁时,它也会负责销毁
它的⼦对象。
2 Qt核心特点
- Qt对标准C++进行了扩展,引入了一些新的概念和功能
- 元对象编译器(MOC: Meta-Object Compiler)是一个预处理器
- 先将Qt的特性程序转换为标准C++程序,再由标准C++编译器进行编译
- 使用信号与槽机制,只有添加Q_OBJECT宏,MOC才能对类里的信号与槽进行预处理
- Qt为C++语言增加的特性在Qt Core模块里实现,由Qt的元对象系统实现。包括:
- 信号与槽机制
- 属性系统
- 动态类型转换
- 前端开发使⽤QML/JaveScript,后端代码开发使⽤Qt C++来完成系统接⼝和繁重的计算⼯作,将设计界⾯的开发者和功
能开发者分开了
2.1 元对象系统(Meta-Object System)
- QObject类是所有使用元对象系统的类的基类
- 在一个类的private部分声明Q_OBJECT宏
- MOC(元对象编译器)为每个QObject的子类提供必要的代码
- QObject::metaObject()返回类关联的元对象
QObject *obj = new QPushButton;
obj->metaObject()->className(); // 返回"QPushButton"
2.2 动态类型转换
- qobject_cast:要求类有Q_OBJECT宏
QObject *obj = new QLabel;
QLabel *label = qobject_cast<QLabel *>(obj);
2.3 属性系统
- Q_PROPERTY宏定义一个返回类型为Type, 名称为name的属性
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
- 如QWidget类中的部分属性声明
Q_PROPERTY(bool focus READ hasFocus)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)
- 使用MEMBER关键字将类成员变量导出为Qt属性。注意:NOTIFY信号必须被指定,这样才能被QML使用
Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
Q_PROPERTY(qreal spacing MEMBER m_spacing NOTIFY spacingChanged)
Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
...
signals:
void colorChanged();
void spacingChanged();
void textChanged(const QString &newText);
private:
QColor m_color;
qreal m_spacing;
QString m_text;
- 一个属性的行为就像一个类的数据成员,但它有通过元对象系统访问的附加功能
- setProperty可以在运行时为类定义一个新的属性,称之为动态属性
QObject *obj = new QPushButton;
object->setProperty("rich", true);
bool isRich = object->property("rich");
2.4 打印调试
-qDebug()
int test = 100;
qDebug() << test;
2.5 信号与槽
2.5.1 连接信号与槽
- 对于如下connect函数:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
- 参数说明:
- sender: 是发射信号的对象的名称
- signal(): 是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数,只需声明,无需定义
- receiver: 是接收信号的对象名称
- slot(): 是槽函数的名称,需要带括号,有参数时还需要指明参数
- SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串
- 一个信号可以连接多个槽
- 多个信号可以连接同一个槽
- 一个信号可以连接另外一个信号
connect(pushButton, SIGNAL(objectNameChanged(QString)),this, SIGNAL(windowTitelChanged(QString)));
- 当信号和槽函数带有参数时,在 connect()函数里,要写明参数的类型,但可以不写参数名称
- 信号与槽的参数个数和类型需要一致
- 在使用信号与槽的类中,必须在类的定义中加入宏 Q_OBJECT
- 当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码
2.5.2 自定义信号及其使用
2.5.3 信号
- 对于每个工作的信号,命名方式都是on + SignalName的标题。
- 当属性的值发生改变时也会发出一个信号,它们的命名方式是:on + PropertyName + Chagned。如:一个宽度(width)属性改变了,你可以使用onWidthChanged: print(width)来得到这个监控这个新的宽度值
2.6 容器
2.7 Qt模块
-
核心基础模块
-
附加模块
3. QML
- QML语⾔:描述了⽤户界⾯元素的形状和⾏为
JavaScript:⽤户界⾯能够使⽤它提供修饰,或实现复杂的逻辑。 - ⼦元素从⽗元素上继承了坐标系统,它的x,y坐标总是相对应于它的⽗元素坐标系统
- ⼀个属性依赖⼀个或多个其它的属性,这种操作称作属性绑定。当它依赖的属性改变时,它的值也会更新
- 添加⾃⼰定义的属性需要使⽤property修饰符,然后跟上类型,名字和可选择的初始化值(property : )
// custom property
property int times: 24
- 另⼀个重要的声明属性的⽅法是使⽤alias关键字(property alias : ),alias关键字允许我们转发⼀个属性或者转发⼀个属性对象⾃⾝到另⼀个作⽤域
// property alias
property alias anotherTimes: thisLabel.times
- 对于每个元素你都可以提供⼀个信号操作,这个操作在属性值改变时被调⽤ (如 :当height(⾼度)改变时会使⽤控制台输出
⼀个信息) - ⼀个元素id应该只在当前⽂档中被引⽤,如果你想向⽂档外提供元素的调⽤,你可以在根元素上使⽤属性导出的⽅式来提供
- QML的(属性绑定)与JavaScript的=(赋值)是不同的,绑定是⼀个协议,并且存在于整个⽣命周期,然⽽JavaScript赋值(=)只会产⽣⼀次效果。当⼀个新的绑定⽣效或者使⽤JavaScript赋值给属性时,绑定的⽣命周期就会结束
3.1 QML锚点
3.2 QML旋转点
3.3 核心元素
- 元素分为:
- 视觉元素:具有几何形状 (如Rectangle)
- 非视觉元素:提供 一般功能,常用于控制视觉元素,如Timer
3.3.1 Item
- Item
- Item是所有视觉元素的基础元素,即所有其它视觉元素都从Item继承
- Item本身并不绘制任何内容,但定义了所有视觉元素的共同属性
- 几何属性: x, y, z, width, height
- 布局处理:anchors
- 键处理:key, focus
- 变换:scale, rotate, transformOrigin
- 视觉:opacity, visible, clip, smooth
- 状态定义:states用于动画状态更改
3.3.2 MouseArea
- MouseArea
- MouseArea是一个不可见矩形,可以在其中捕获鼠标事件
3.3.3 Component
- Component (组件)
- 组件是可重用的元素,此元素使用基本元素组成一个功能完整的、可重用的组件,如输入密码框
- 一个QML文件就是一个组件,其组件名为QML的文件名
- 在根级添加属性导出⽅便使⽤者修改它
- 使⽤了QML的alias(别名)的功能将内部嵌套的QML元素的属性导出到外⾯使⽤
- 只有根级目录的属性才能够被其它⽂件的组件访问
// Button.qml
import QtQuick 2.0
Rectangle {
id: root
// export button properties
property alias text: label.text
signal clicked
//
width: 116; height: 26
color: "lightsteelblue"
border.color: "slategrey"
Text {
id: label
anchors.centerIn: parent
text: "Start"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.clicked()
}
}
}
3.3.4 定位器
- 定位器
- Row
- Column
- Grid
- Flow
3.3.5 Model-View
3.3.6 QML与C++联动
- 上下⽂属性使⽤对于⼩型的应⽤程序使⽤⾮常⽅便。它们不需要你做太多的事情就可以将系统编程接⼝暴露为友好的全局对象。
- 注册QML类型允许⽤户从QML中控制⼀个c++对象的⽣命周期。上下⽂属性⽆法完成此任务
3.3.6.1 方法一:引擎注册类型
- 通过qmlRegisterType函数把C++类注册到QML中,然后QML可以使用此类
- 在main函数中调用
QQmlApplicationEngine engine;
qmlRegisterType<CurrentTime>("org.example", 1, 0, "CurrentTime");
engine.load(source);
- main.qml中
import org.example 1.0
CurrentTime {
// access properties, functions, signals
}
3.3.6.2 方法二:上下⽂属性
- 在main函数中
QScopedPointer<CurrentTime> current(new CurrentTime());
QQmlApplicationEngine engine;
engine.rootContext().setContextProperty("current", current.value())
engine.load(source);
- main.qml中
import QtQuick 2.4
import QtQuick.Window 2.0
Window {
visible: true
width: 512
height: 300
Component.onCompleted: {
console.log('current: ' + current)
}
}
3.3.7 暴露C++属性、方法和信号给QML
- 参考QML与C++ 集成概览
- 为了给QML提供一些C++ 数据和函数,必须从QObject继承类。由于 QML 引擎与元对象系统的集成,可以从QML中访问任何从QObject继承的类的属性、方法和信号,在将C++ 类的属性暴露给QML有详细描述。一旦所需要的功能可以由类来提供,则有以下方法将其暴露给QML:
- 类可以被注册为可实例化的QML类型,这样它就可以被实例化,并象使用普通QML 对象类型那样,在QML中使用它了
- 类可以注册为单例类型,这样类的单例就可以从QML代码中导入,允许从QML中访问实例的属性、方法 和 信号
- 类的实例可以以context 属性或context对象 的形式被嵌入QML代码,允许从QML中访问实例的属性、方法 和 信号
- 为C++ 和QML选择正确的集成方法
3.3.7.1 在C++头文件中定义
-
Q_PROPERTY
- 在C++头文件中定义QML可访问的变量(或属性)
-
Q_INVOKABLE
- 在C++头文件中定义QML可调用的函数
-
signals
- C++调用此函数发信号,这些函数无返回值,且不用实现
-
slots
- 数据更新槽,由信号触发
3.3.7.2 将C++ 类的Element类型暴露给QML(Q_PROPERTY)
- 由于QML引擎与Qt元对象系统的集成,QML很容易从C++ 端扩展
- 这种集成可以让继承自QObject的类的属性、方法和信号从QML中访问:
- 属性可以读取和修改
- 方法可以被JavaScript表达式调用,必要时信号处理函数可以自动创建
- 继承自QObject的类的枚举值可以从QML中访问
class Message : public QObject
{
Q_OBJECT
Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
public:
void setAuthor(const QString &a) {
if (a != m_author) {
m_author = a;
emit authorChanged();
}
}
QString author() const {
return m_author;
}
signals:
void authorChanged();
private:
QString m_author;
};
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView view;
Message msg;
view.engine()->rootContext()->setContextProperty("msg", &msg);
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
return app.exec();
}
import QtQuick 2.0
Text {
width: 100; height: 100
text: msg.author // invokes Message::author() to get this value
Component.onCompleted: {
msg.author = "Jonah" // invokes Message::setAuthor()
}
}
3.3.7.3 将C++ 类的Object类型暴露给QML(Q_PROPERTY)
class Message : public QObject
{
Q_OBJECT
Q_PROPERTY(MessageBody* body READ body WRITE setBody NOTIFY bodyChanged)
public:
MessageBody* body() const;
void setBody(MessageBody* body);
};
class MessageBody : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE text NOTIFY textChanged)
// ...
}
Message {
body: MessageBody {
text: "Hello, world!"
}
}
3.3.7.4 将C++ 类的Object-List类型暴露给QML(Q_PROPERTY)
class MessageBoard : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
public:
QQmlListProperty<Message> messages();
private:
static void append_message(QQmlListProperty<Message> *list, Message *msg);
QList<Message *> m_messages;
};
QQmlListProperty<Message> MessageBoard::messages()
{
return QQmlListProperty<Message>(this, 0, &MessageBoard::append_message);
}
void MessageBoard::append_message(QQmlListProperty<Message> *list, Message *msg)
{
MessageBoard *msgBoard = qobject_cast<MessageBoard *>(list->object);
if (msg)
msgBoard->m_messages.append(msg);
}
3.3.7.5 将C++ 类的Grouped 类型暴露给QML(Q_PROPERTY)
class MessageAuthor : public QObject
{
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QString email READ email WRITE setEmail)
public:
...
};
class Message : public QObject
{
Q_OBJECT
Q_PROPERTY(MessageAuthor* author READ author)
public:
Message(QObject *parent)
: QObject(parent), m_author(new MessageAuthor(this))
{
}
MessageAuthor *author() const {
return m_author;
}
private:
MessageAuthor *m_author;
};
Message {
author.name: "Alexandra"
author.email: "alexandra@mail.com"
}
3.3.7.6 将C++ 类的public方法暴露给QML( Q_INVOKABLE和Qt Slots)
- 带有 Q_INVOKABLE宏的public方法
- public slots:中的方法
class MessageBoard : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE bool postMessage(const QString &msg) {
qDebug() << "Called the C++ method with" << msg;
return true;
}
public slots:
void refresh() {
qDebug() << "Called the C++ slot";
}
};
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
MessageBoard msgBoard;
QQuickView view;
view.engine()->rootContext()->setContextProperty("msgBoard", &msgBoard);
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
return app.exec();
}
- qml文件
// MyItem.qml
import QtQuick 2.0
Item {
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: {
var result = msgBoard.postMessage("Hello from QML")
console.log("Result of postMessage():", result)
msgBoard.refresh();
}
}
}
3.3.7.7 将C++ 类的Signals暴露给QML
class MessageBoard : public QObject
{
Q_OBJECT
public:
// ...
signals:
void newMessagePosted(const QString &subject);
};
- 在qml中接收newMessagePosted信号
MessageBoard {
onNewMessagePosted: (subject)=> console.log("New message received:", subject)
}
3.3.7.8 QML与C++ 间的数据类型转换
3.4 QObject对象
- QObject允许使⽤ 其派生对象的⽤户连接槽函数并且当属性变化时获得通知
- 定义
class Person : public QObject
{
Q_OBJECT // enabled meta object abilities
// property declarations required for QML
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(Gender gender READ gender WRITE setGender NOTIFY genderChanged)
// enables enum introspections
Q_ENUMS(Gender)
public:
// standard Qt constructor with parent for memory management
Person(QObject *parent = 0);
enum Gender { Unknown, Male, Female, Other };
QString name() const;
Gender gender() const;
public slots: // slots can be connected to signals
void setName(const QString &);
void setGender(Gender);
signals: // signals can be emitted
void nameChanged(const QString &name);
void genderChanged(Gender gender);
private:
// data members
QString m_name;
Gender m_gender;
};
- 实现
Person::Person(QObject *parent)
: QObject(parent)
, m_gender(Person::Unknown)
{
}
QString Person::name() const
{
return m_name;
}
void Person::setName(const QString &name)
{
if (m_name != name) // guard
{
m_name = name;
emit nameChanged(m_name);
}
}