原文地址:【秒懂音视频开发】06_Qt开发基础 - M了个J - 博客园
1.pro文件的配置
1.1 跨平台配置
之前我们分别在Windows、Mac环境的Qt项目中集成了FFmpeg。
可以发现在.pro文件的配置中,FFmpeg库在Mac、Windows上的位置是有所差异的。这样就会导致.pro文件无法跨平台使用。
# windows | |
INCLUDEPATH += F:/Dev/ffmpeg-4.3.2/include | |
# mac | |
INCLUDEPATH += /usr/local/Cellar/ffmpeg/4.3.2/include |
为了实现跨平台配置,可以在配置前面加上平台标识的前缀,表示这个配置只会在对应的平台生效。
# windows | |
win32:INCLUDEPATH += F:/Dev/ffmpeg-4.3.2/include | |
win32:LIBS += -LF:/Dev/ffmpeg-4.3.2/lib \ | |
-lavcodec \ | |
-lavdevice \ | |
-lavfilter \ | |
-lavformat \ | |
-lavutil \ | |
-lpostproc \ | |
-lswscale \ | |
-lswresample | |
# mac | |
macx:INCLUDEPATH += /usr/local/Cellar/ffmpeg/4.3.2/include | |
macx:LIBS += -L/usr/local/Cellar/ffmpeg/4.3.2/lib \ | |
-lavcodec \ | |
-lavdevice \ | |
-lavfilter \ | |
-lavformat \ | |
-lavutil \ | |
-lpostproc \ | |
-lswscale \ | |
-lswresample \ | |
-lavresample | |
# linux | |
# linux:INCLUDEPATH += ... | |
# linux:LIBS += ... |
以后针对每个平台的配置可能会比较多,可以使用大括号来简化。
# windows | |
win32 { | |
INCLUDEPATH += F:/Dev/ffmpeg-4.3.2/include | |
LIBS += -LF:/Dev/ffmpeg-4.3.2/lib \ | |
-lavcodec \ | |
-lavdevice \ | |
-lavfilter \ | |
-lavformat \ | |
-lavutil \ | |
-lpostproc \ | |
-lswscale \ | |
-lswresample | |
} | |
# mac | |
macx { | |
INCLUDEPATH += /usr/local/Cellar/ffmpeg/4.3.2/include | |
LIBS += -L/usr/local/Cellar/ffmpeg/4.3.2/lib \ | |
-lavcodec \ | |
-lavdevice \ | |
-lavfilter \ | |
-lavformat \ | |
-lavutil \ | |
-lpostproc \ | |
-lswscale \ | |
-lswresample \ | |
-lavresample | |
} |
折叠
1.2 自定义变量
可以将公共的信息抽取成变量,然后使用$${}去访问。
# mac | |
macx { | |
FFMPEG_HOME = /usr/local/Cellar/ffmpeg/4.3.2 | |
INCLUDEPATH += $${FFMPEG_HOME}/include | |
LIBS += -L$${FFMPEG_HOME}/lib \ | |
-lavcodec \ | |
-lavdevice \ | |
-lavfilter \ | |
-lavformat \ | |
-lavutil \ | |
-lpostproc \ | |
-lswscale \ | |
-lswresample \ | |
-lavresample | |
} |
1.3 读取系统环境变量
也可以通过$$()读取系统的环境变量。比如,我的Windows中有个叫做JAVA_HOME的环境变量。
环境变量
# 使用message打印环境变量JAVA_HOME的值 | |
message($$(JAVA_HOME)) |
最后可以在概要信息处看到JAVA_HOME的打印结果。
概要信息
2. 控件的基本使用
为了更好地学习Qt控件的使用,建议创建项目时先不要生成ui文件。
不生成ui文件
打开mainwindow.cpp,在MainWindow的构造函数中编写界面的初始化代码。
2.1 窗口设置
MainWindow::MainWindow(QWidget *parent) | |
: QMainWindow(parent) { | |
// 设置窗口标题 | |
setWindowTitle("主窗口"); | |
// 设置窗口大小 | |
// 窗口可以通过拖拽边缘进行自由伸缩 | |
// resize(400, 400); | |
// 设置窗口的固定大小 | |
// 窗口不能通过拖拽边缘进行自由伸缩 | |
setFixedSize(400, 400); | |
// 设置窗口的位置 | |
// 以父控件的左上角为坐标原点 | |
// 没有父控件,就以屏幕的左上角作为坐标原点 | |
move(100, 100); | |
} |
Qt坐标系如下图所示。
Qt坐标系
2.2 添加子控件
#include <QPushButton> | |
// 创建按钮 | |
QPushButton *btn = new QPushButton; | |
// 设置按钮的文字 | |
btn->setText("登录"); | |
// 设置父控件为当前窗口 | |
btn->setParent(this); | |
// 设置按钮的位置和大小 | |
btn->move(50, 50); | |
btn->resize(100, 50); | |
// 创建第2个按钮 | |
new QPushButton("注册", this); |
new出来的Qt控件是不需要程序员手动delete的,Qt内部会自动管理内存:当父控件销毁时,会顺带销毁子控件。
3. 信号与槽
3.1 基本使用
- 信号(Signal):比如点击按钮就会发出一个点击信号
- 槽(Slot):一般也叫槽函数,是用来处理信号的函数
- 官方文档参考:Signals & Slots
信号与槽
上图中的效果是:
- Object1发出信号signal1,交给Object2的槽slot1、slot2去处理
- Object1是信号的发送者,Object2是信号的接收者
- Object1发出信号signal2,交给Object4的槽slot1去处理
- Object1是信号的发送者,Object4是信号的接收者
- Object3发出信号signal1,交给Object4的槽slot3去处理
- Object3是信号的发送者,Object4是信号的接收者
- 1个信号可以由多个槽进行处理,1个槽可以处理多个信号
通过connect函数可以将信号的发送者、信号、信号的接收者、槽连接在一起。
connect(信号的发送者, 信号, 信号的接收者, 槽); | |
// 比如点击按钮,关闭当前窗口 | |
// btn发出clicked信号,就会调用this的close函数 | |
connect(btn, &QPushButton::clicked, this, &MainWindow::close); | |
// 可以通过disconnect断开连接 | |
disconnect(btn, &QPushButton::clicked, this, &MainWindow::close); |
3.2 自定义信号与槽
信号的发送者和接收者都必须继承自QObject,Qt中的控件最终都是继承自QObject,比如QMainWindow、QPushButton等。
3.2.1 信号的发送者
- sender.h
- Q_OBJECT用以支持自定义信号和槽
- 自定义的信号需要写在signals:下面
- 自定义的信号只需要声明,不需要实现
#ifndef SENDER_H | |
#define SENDER_H | |
#include <QObject> | |
class Sender : public QObject { | |
Q_OBJECT | |
public: | |
explicit Sender(QObject *parent = nullptr); | |
// 自定义信号 | |
signals: | |
void exit(); | |
}; | |
#endif // SENDER_H |
- sender.cpp
#include "sender.h" | |
Sender::Sender(QObject *parent) : QObject(parent) { | |
} |
3.2.2 信号的接收者
- receiver.h
- 自定义的槽建议写在public slots:下面
#ifndef RECEIVER_H | |
#define RECEIVER_H | |
#include <QObject> | |
class Receiver : public QObject { | |
Q_OBJECT | |
public: | |
explicit Receiver(QObject *parent = nullptr); | |
// 自定义槽 | |
public slots: | |
void handleExit(); | |
}; | |
#endif // RECEIVER_H |
- receiver.cpp
#include "receiver.h" | |
#include <QDebug> | |
Receiver::Receiver(QObject *parent) : QObject(parent) { | |
} | |
// 实现槽函数,编写处理信号的代码 | |
void Receiver::handleExit() { | |
qDebug() << "Receiver::handleExit()"; | |
} |
3.2.3 连接
- mainwindow.h
#ifndef MAINWINDOW_H | |
#define MAINWINDOW_H | |
#include <QMainWindow> | |
class MainWindow : public QMainWindow { | |
Q_OBJECT | |
public: | |
MainWindow(QWidget *parent = nullptr); | |
~MainWindow(); | |
}; | |
#endif // MAINWINDOW_H |
- mainwindow.cpp
#include "mainwindow.h" | |
#include "sender.h" | |
#include "receiver.h" | |
MainWindow::MainWindow(QWidget *parent) | |
: QMainWindow(parent) { | |
// 创建对象 | |
Sender *sender = new Sender; | |
Receiver *receiver = new Receiver; | |
// 连接 | |
connect(sender, | |
&Sender::exit, | |
receiver, | |
&Receiver::handleExit); | |
// 发出信号 | |
// 最终会调用Receiver::handleExit函数 | |
emit sender->exit(); | |
// 销毁对象 | |
delete sender; | |
delete receiver; | |
} | |
MainWindow::~MainWindow() { | |
} |
3.2.4 参数和返回值
信号与槽都可以有参数和返回值:
- 发信号时的参数会传递给槽
- 槽的返回值会返回到发信号的位置
// 自定义信号 | |
signals: | |
int exit(int a, int b); | |
// 自定义槽 | |
public slots: | |
int handleExit(int a, int b); | |
int Receiver::handleExit(int a, int b) { | |
// Receiver::handleExit() 10 20 | |
qDebug() << "Receiver::handleExit()" << a << b; | |
return a + b; | |
} | |
// 发出信号 | |
int a = emit sender->exit(10, 20); | |
// 30 | |
qDebug() << a; |
需要注意的是:信号的参数个数必须大于等于槽的参数个数。比如下面的写法是错误的:
// 自定义信号 | |
signals: | |
void exit(int a); | |
// 自定义槽 | |
public slots: | |
void handleExit(int a, int b); |
3.3 连接2个信号
比如下图,连接了Object 1的Signal 1A和Object 2的Signal 2A
- 当Object 1发出Signal 1A时,会触发Slot X、Slot Y
- 当Object 2发出Signal 2A时,只会触发Slot Y,而不会触发Slot X
连接2个信号
可以利用connect函数连接2个信号
- 当sender发出exit信号时,sender2会发出exit2信号
- 当sender2发出exit2信号时,sender并不会发出exit信号
connect(sender, | |
&Sender::exit, | |
sender2, | |
&Sender2::exit2); |
3.4 Lambda
也可以直接使用Lambda处理信号。
connect(sender, &Sender::exit, []() { | |
qDebug() << "lambda handle exit"; | |
}); |
3.5 ui文件
如果你的控件是通过ui文件生成的,连接槽函数的步骤会更加简单。
首先建议给按钮们起个有意义的变量名,比如分别叫做:loginButton、registerButton。
起名
对着登录按钮右键,选择转为槽。
转为槽
选择clicked信号,然后OK。
clicked
此时,Qt Creator已经帮你自动生成了槽函数的声明和实现,当我们点击登录按钮时,就会调用这个函数。
class MainWindow : public QMainWindow { | |
Q_OBJECT | |
private slots: | |
// 槽函数的声明 | |
void on_loginButton_clicked(); | |
}; | |
// 槽函数的实现 | |
void MainWindow::on_loginButton_clicked() { | |
qDebug() << "on_loginButton_clicked"; | |
} |
其实,认真观察函数名可以发现一个规律,函数名的命名规则是:on_控件的变量名_事件名。
于是,我们可以尝试编写以下代码。
class MainWindow : public QMainWindow { | |
Q_OBJECT | |
private slots: | |
// 槽函数的声明 | |
void on_registerButton_clicked(); | |
}; | |
// 槽函数的实现 | |
void MainWindow::on_registerButton_clicked() { | |
qDebug() << "on_registerButton_clicked"; | |
} |
然后,你点击一下注册按钮,会发现成功调用了MainWindow::on_registerButton_clicked函数。
于是得知:ui文件中的控件会自动跟符合命名规则的槽函数建立连接。
最后,再提示一个知识点:ui文件中的控件可以在代码中通过ui->变量名访问。
MainWindow::MainWindow(QWidget *parent) | |
: QMainWindow(parent), ui(new Ui::MainWindow) { | |
ui->setupUi(this); | |
// 通过ui->访问ui文件中的2个按钮 | |
ui->loginButton->setFixedSize(100, 30); | |
ui->registerButton->setFixedSize(100, 30); | |
} |