.pro文件的配置
1.1 跨平台配置
总所周知,Mac
和Windows
平台存在差异,FFmpeg
库在其上的位置也不相同,这样就会导致.pro
文件无法跨平台使用。
# windows
INCLUDEPATH += F:/Dev/ffmpeg-6.0_2/include
# mac
INCLUDEPATH += /opt/homebrew/Cellar/ffmpeg/6.0_2/include
为了实现配置的跨平台使用,可以在配置前面加上平台标识前缀,表示该配置只在对应平台生效。
# windows
win32:INCLUDEPATH += F:/Dev/ffmpeg-6.0_2/include
win32:LIBS += -LF:/Dev/ffmpeg-6.0_2/lib \
-lavcodec \
-lavdevice \
-lavfilter \
-lavformat \
-lavutil \
-lpostproc \
-lswscale \
-lswresample
# mac
macx:INCLUDEPATH += /opt/homebrew/Cellar/ffmpeg/6.0_2/include
macx:LIBS += -L/opt/homebrew/Cellar/ffmpeg/6.0_2/lib \
-lavcodec \
-lavdevice \
-lavfilter \
-lavformat \
-lavutil \
-lpostproc \
-lswscale \
-lswresample \
-lavresample
# linux
# linux:INCLUDEPATH += ...
# linux:LIBS += ...
实际开发中,针对每个平台的配置可能比较多,可以使用大括号来简化区分对应配置。
# windows
win32 {
INCLUDEPATH += F:/Dev/ffmpeg-6.0_2/include
LIBS += -LF:/Dev/ffmpeg-6.0_2/lib \
-lavcodec \
-lavdevice \
-lavfilter \
-lavformat \
-lavutil \
-lpostproc \
-lswscale \
-lswresample
}
# mac
# mac {} / mac: {} / macx {} / macx: {}都可以
macx {
INCLUDEPATH += /opt/homebrew/Cellar/ffmpeg/6.0_2/include
LIBS += -L/opt/homebrew/Cellar/ffmpeg/6.0_2/lib \
-lavcodec \
-lavdevice \
-lavfilter \
-lavformat \
-lavutil \
-lpostproc \
-lswscale \
-lswresample \
-lavresample
}
1.2 自定义变量
为了更好的开发和阅读代码,可以将公共的信息抽取成变量,然后通过$${变量}
来访问。
# mac
macx {
// 抽取 FFmpeg 路径
FFMPEG_PATH = /opt/homebrew/Cellar/ffmpeg/6.0_2
INCLUDEPATH += $${FFMPEG_PATH}/include
LIBS += -L$${FFMPEG_PATH}/lib \
-lavcodec \
-lavdevice \
-lavfilter \
-lavformat \
-lavutil \
-lpostproc \
-lswscale \
-lswresample \
-lavresample
}
1.3 读取环境变量
在.pro
文件中,通过message($$(变量))
可以读取系统环境变量的值,通过message($${变量})
可以读取用户设置变量的值,修改完.pro
文件后,重新编译,在概要信息处就能查看打印结果。
2 控件的基本使用
我们先简单创建个项目,不使用UI
文件,来简单了解下Qt
控件的使用。
2.1 窗口设置
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 设置窗口标题
setWindowTitle("主窗口");
// 设置窗口大小,可以通过拖拽边缘进行自由伸缩
// resize(400, 400);
// 设置窗口的固定大小,不能拖拽伸缩
setFixedSize(400, 400);
// 设置窗口位置,以父控件的左上角为坐标原点,没有父控件,就以屏幕左上角为坐标原点
move(100, 100);
}
2.2 添加子控件
#include <QPushButton>
// 创建按钮,C++中new QPushButton()的小括号可以省略,因为没有传参数
QPushButton *loginBtn = new QPushButton;
// 设置按钮的文字
loginBtn->setText("登录");
// 设置按钮父控件
loginBtn->setParent(this);
// 设置按钮的位置和大小
loginBtn->move(50, 50);
loginBtn->resize(100, 50);
// 创建第二个按钮
QPushButton *regisBtn = new QPushButton("注册", this);
regisBtn->move(200, 50);
regisBtn->resize(100, 50);
- 此处
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
函数连接在一起,可以通过disconnect
函数断开连接。
connect(信号的发送者, 信号, 信号的接收者, 槽);
#include "mainwindow.h"
#include "QPushButton"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 设置窗口大小
resize(500, 500);
QPushButton *btn = new QPushButton;
btn->setText("关闭");
btn->setFixedSize(100, 50);
btn->move(200, 200);
btn->setParent(this);
// 比如点击按钮,关闭当前窗口
// btn发出clicked信号,就会调用this的close函数
// clicked信号为QPushButton父类QAbstractButton自带
// close槽函数为MainWindow父类的父类QWidget自带
connect(btn, &QPushButton::clicked, this, &MainWindow::close);
// 可以通过disconnect断开连接
//disconnect(btn, &QPushButton::clicked, this, &MainWindow::close);
}
MainWindow::~MainWindow() {}
3.2 自定义信号和槽
Qt
中信号的发送者和接收者都必须继承自QObject
,Qt
中的控件最终都是继承自QObject
,比如QMainWindow
、QPushButton
等,也就是说这套API
的基类就是QObject
,就像OC
中的NSObject
。
3.2.1 信号的发送者
自定义一个继承自QObject
的信号的发送者类为Sender
。
-
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 信号的接收者
自定义一个继承自QObject
的信号的接收者类为Receiver
。
-
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
中实例化Sender
和Receiver
的对象,连接信号与槽。
- 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)
{
qDebug() << "Receiver::handleExit()" << a << b;
return a + b;
}
// 发出信号
int a = emit sender->exit(5, 10);
// 输出 15
qDebug() << a;
⚠️⚠️⚠️⚠️⚠️注意:信号的参数个数必须大于等于槽的参数个数。比如下面的写法是错误的:
// 自定义信号
signals:
int exit(int a);
// 自定义槽
public slots:
int handleExit(int a, int b);
3.3 连接两个信号
两个信号可以进行连接,比如下图,连接了Object 1
的Signal 1A
和Object 2
的Signal 2A
。
- 当
Object 1
发出Signal 1A
时,会触发Slot X
、Slot Y
。 - 当
Object 2
发出Signal 2A
时,只会触发Slot Y
,而不会触发Slot X
。
利用connect
函数可以连接两个信号。
connect(sender,
&Sender::exit,
sender2,
&Sender2::exit2);
信号的连接是单向的:
- 当
sender
发出exit
信号时,sender2
会发出exit2
信号。 - 当
sender2
发出exit2
信号时,sender
并不会发出exit
信号。
3.4 Lambda
除了定义槽函数处理信号外,还可以直接使用Lambda
函数(匿名函数)来处理信号。类似block
或闭包
。
connect(sender, &Sender::exit, [](int a, int b) ->int {
qDebug() << "Lambda handle exit";
return a + b;
});
- []是捕获说明符,()是参数列表,->int是返回类型,如果没有返回类型,则可以省略,{}是函数体部分。
3.5 UI
文件
如果是通过UI
文件生成的控件,那么连接槽函数的步骤也比较简单。
首先,给按钮设定对象名称,如下图的loginBtn
、regisBtn
。
然后,右键点击登录
按钮,选择转到槽
。
选择clicked
信号,点击确定
。
至此,Qt Creator
就会帮我们自动生成槽函数的声明和实现,当我们点击登录
按钮时,就会调用这个函数。
class MainWindow : public QMainWindow
{
Q_OBJECT
...
private slots:
// 槽函数的声明
void on_loginBtn_clicked();
}
// 槽函数的实现
void MainWindow::on_loginBtn_clicked()
{
qDebug() << "on_loginBtn_clicked";
}
认真观察Qt Creator
生成的槽函数名就会发现其命名规则是:on_控件的对象名称_事件名。
那么,我们根据命名规则,为注册按钮编写槽函数。
class MainWindow : public QMainWindow
{
Q_OBJECT
...
private slots:
// 槽函数的声明
void on_regisBtn_clicked();
}
// 槽函数的实现
void MainWindow::on_regisBtn_clicked()
{
qDebug() << "on_regisBtn_clicked";
}
运行程序,点击注册
按钮,会发现成功调用了MainWindow::on_regisBtn_clicked()
函数。
于是可知:UI文件中的控件会自动跟符合命名规则的槽函数建立连接。
最后,再提示一个知识点:UI
文件中的控件可以在代码中通过ui->对象名称访问。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 通过ui->访问ui文件中的2个按钮
ui->loginBtn->setFixedSize(100,50);
ui->regisBtn->setFixedSize(100, 50);
}