1. 自定义控件
1.1 创建自定义控件
1.在项目目录上右键, 选择 "Add New"
2.选择 "Qt" --> "Qt 设计师界面类"
3.根据需求选择模板,此处选择空窗口
4.设置类名 和 相关文件名
使用设计师界面类会产生三个文件:.h .cpp .ui
5.通过过ui文件设置控件
6.在主窗口上设置 SoundWidget 控件
① 打开主窗口 ui 界面 (Widget.ui)
② 将 Widget 拖拽到主窗口中
③ 在 Widget 上点击右键,再选择 "提升为..."
④ 填写自定义控件类名,再点击提升
1.2 功能设置
-
调整数字同时会影响水平滚动条
-
调整水平滚动条同时影响数字
cpp
// 由于 QSpinBox 的信号有两个重载版本,所以需要提前定义信号
void(QSpinBox:: * spSignal)(int) = &QSpinBox::valueChanged;
connect(ui->spinBox, spSignal, ui->horizontalSlider, &QSlider::setValue);
connect(ui->horizontalSlider, &QSlider::valueChanged, ui->spinBox, &QSpinBox::setValue);
2. 事件概述
-
事件 与 信号&槽的功能类似
-
事件 VS 信号&槽
-
事件与信号是两个不同层面的东西,发出者不同,作用对象也不同。
-
事件由外部实体 ( 鼠标、键盘等 ) 生成,事件更偏底层。
-
信号由按钮等 Qt 对象生成,例如:按钮的 clicked 信号。
-
使用 Qt 内置组件,一般使用信号&槽; 使用自定义组件时,一般使用事件
-
-
QWidget 是所有控件的父类,在 Protected Functions 中提供了各种事件的虚函数。子类在继承父类时,自己实现所需要的事件虚函数。
- 常用事件: 鼠标事件、键盘事件、定时事件、上下文菜单事件、关闭事件、拖放事件、绘制事件等。
QWidget 中的事件定义:
-
事件对象:事件当中的参数叫做事件对象,内部保存了和事件相关的数据
- QEvent 是事件对象的基类,其他事件对象都继承该对象。例如: QMouseEvent、QKeyEvent、QWheelEvent等。
举个栗子:
事件与信号并不相同,例如我们使用鼠标点击了一下界面上的按钮,那么就会产生鼠标事件QMouseEvent(不是按钮产生的),而因为按钮被按下了,所以它会发出clicked()单击信号(是按钮产生的)。这里一般只关心按钮的单击信号,而不用考虑鼠标事件,但是如果要设计一个按钮,当鼠标点击按钮时让它产生别的效果,那么就要关心鼠标事件了。
可以看出,事件与信号是两个不同层面的东西,它们的发出者不同,作用也不同。事件由外部实体(例如,按下键盘键、鼠标滚轮)生成,事件更底层。信号需要关注的是产生其的对象(例如按钮),槽函数需要找到信号对象,不会关心如何产生这个信号。如果我们使用已有组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。在Qt中,任何QObject的子类的实例都可以接收和处理事件。
信号槽:signal由具体对象发出,然后会马上交给由connect函数连接的slot进行处理。
事件:Qt使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件进行处理。但是必要的时候Qt的事件也是可以不进入事件队列,而是直接处理,并且事件还可以使用"事件过滤器"进行过滤。
3. 鼠标事件
3.1 鼠标事件
-
void enterEvent(QEvent *ev): 鼠标进入事件
-
void leaveEvent(QEvent *ev): 鼠标离开事件
-
void mousePressEvent(QMouseEvent *ev); 鼠标按下事件
-
void mouseReleaseEvent(QMouseEvent *ev); 鼠标释放事件
-
void mouseMoveEvent(QMouseEvent *ev); 鼠标移动事件
1.创建自定义组件
2.在ui 结构中调用 MyLabel 控件并提升
注意:要提升为 QLabel 类型
3.再头文件中声明事件函数
cpp
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QLabel>
class MyLabel : public QLabel
{
Q_OBJECT
public:
explicit MyLabel(QWidget *parent = nullptr);
// 鼠标进入 和 鼠标离开事件
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);
// 鼠标按下、鼠标离开、鼠标移动事件
void mousePressEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
void mouseMoveEvent(QMouseEvent *ev);
signals:
public slots:
};
#endif // MYLABEL_H
4.在mylabel.cpp 文件中实现事件
cpp
#include <QMouseEvent>
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
// 设置鼠标追踪时,不需要进行点击也能捕获到鼠标移动事件
this->setMouseTracking(true);
}
void MyLabel::enterEvent(QEvent *e)
{
qDebug() << "我进来了";
}
void MyLabel::leaveEvent(QEvent *e)
{
qDebug() << "我出来了";
}
void MyLabel::mousePressEvent(QMouseEvent *e)
{
qDebug() << "打我啊笨蛋";
}
void MyLabel::mouseReleaseEvent(QMouseEvent *e)
{
qDebug() << "啊,我被打了";
}
void MyLabel::mouseMoveEvent(QMouseEvent *e)
{
qDebug() << "鼠标在移动";
}
3.2 鼠标事件对象
QMouseEvent 事件对象中保存了一些数据
-
button() : 方法能够获取当前使用的是鼠标的哪个按钮,用来区分鼠标的左右键和滚轴
-
pos() \ x() \ y() : 方法能够获取鼠标在组件范围内的坐标
-
windowPos() : 该方法能够获取鼠标在程序窗口中的位置坐标
-
screenPos() : 该方法能够获取鼠标在显示中的位置坐标
cpp
void MyLabel::mousePressEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton)
{
qDebug() << "打我啊笨蛋" << "左键";
qDebug() << e->pos() << e->x() << e->y();
qDebug() << e->windowPos();
qDebug() << e->screenPos();
}
else if (ev->button() == Qt::RightButton)
{
qDebug() << "鼠标右键";
}
else if (ev->button() == Qt::MidButton)
{
qDebug() << "鼠标滚轴按下";
}
}
3.3 滚轴事件
-
void wheelEvent(QWheelEvent *e) : 滚轴上下滚动时触发
-
事件对象重要方法:QPoint angleDelta() 获取向上滚动 或者 向下滚动的角度
cpp
void MyLabel::wheelEvent(QWheelEvent *e)
{
qDebug() << e->angleDelta() << e->angleDelta().rx() << e->angleDelta().ry();
if (e->angleDelta().y() > 0)
{
this->setNum(++num);
}
else {
this->setNum(--num);
}
}
4. 键盘事件
-
keyPressEvent(QKeyEvent *event) : 键盘按下事件
-
keyReleaseEvent(QKeyEvent *event) : 键盘弹起事件
-
当键盘按下或者弹起时,键盘事件便会被发送给拥有键盘输入焦点的部件,例如:QLineEdit。
-
键盘事件对象常用方法:
- key() : 获取当前按键的 ascii 码值
-
注意事项:
-
事件会阻止 QLineEdit 的原功能,需要将事件对象return回去以便让原功能正常使用
-
大键盘有两个回车键,主键盘的回车键匹配 Qt::Key_Return,小键盘的回车键 匹配 Qt::Key_Enter
-
示例:创建一个自定义的 QInput ,继承与 QLineEdit
MyInput 头文件中定义事件函数
cpp
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
MyInput 源文件中实现事件函数
cpp
void MyInput::keyPressEvent(QKeyEvent *event){
qDebug() << "按下" << e->key();
switch (e->key())
{
// 匹配esc键
case Qt::Key_Escape:
qDebug() << "按下了esc键";
break;
// 匹配回车键
case Qt::Key_Return:
qDebug() << "按下了enter键";
break;
}
return QLineEdit::keyPressEvent(event);
}
void MyInput::keyReleaseEvent(QKeyEvent *event)
{
qDebug() << "键盘弹起" << event->key();
}
6. 定时器
6.1 timerEvent
-
timerEvent(QTimerEvent *e) : 定时器事件,继承自 QObject
-
int timerId = startTimer(int num) : 启动定时器,每隔 num 毫秒后执行一次
-
killTimer(int timerId) : 关闭定时器
cpp
Widget::Widget(QWidget *parent) : QWidget(parent), Wui(new Ui::Widget)
{
ui->setupUi(this);
// 启动定时器
startTimer(1000);
}
void Widget::timerEvent(QTimerEvent *e)
{
static int num1 = 1;
ui->l1->setText(QString::number(num1++));
}
区分多个定时器:
-
核心: e->timerId() 方法用来获取定时器的 id
-
timer1 、 timer2 需要全局定义,放在 头文件 中
cpp
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
// 启动定时器1
timer1 = startTimer(1000);
// 启动定时器2
timer2 = startTimer(2000);
}
void Widget::timerEvent(QTimerEvent *e)
{
if(e->timerId() == timer1)
{
static int num1 = 1;
ui->l1->setText(QString::number(num1++));
}
if(e->timerId() == timer2)
{
static int num2 = 1;
ui->l2->setText(QString::number(num2++));
}
}
6.2 QTimer(推荐)
-
QTimer 定时器直接继承与 QObject
-
使用信号&槽来处理定时器
- timeout (信号) : 每到一次指定的时间就执行一次代码
-
start(int msec) : 方法能够启动定时器
-
stop方法能够停止定时器
cpp
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
...
// 另一种方式
QTimer *timer3 = new QTimer(this);
timer3->start(500);
// 使用信号和槽实现定时功能
connect(timer3, &QTimer::timeout, [this](){
static int num3 = 1;
ui->l3->setText(QString::number(num3++));
});
}
6.3 案例
案例1: 计时器
实现步骤:
-
页面布局: 一个 QLabel 用来显示时间, 一个 QPushButton 用来控制计时器的启动和停止
-
设置定时器(QTimer)
3)在启动按钮上配置点击信号和槽
cpp
// flag 标记是否开始计时
// true 时,说明开始计时(正在计时),按钮应该显示 暂停
// false 时,说明暂停计时, 按钮应该显示 开始
QTimer *timer4 = new QTimer(this);
connect(ui->btn, &QPushButton::clicked, [=](){
if (flag)
{
timer4->start(1000);
ui->btn->setText("暂停");
flag = false;
}
else
{
timer4->stop();
ui->btn->setText("开始");
flag = true;
}
});
connect(timer4, &QTimer::timeout, [this](){
// 使用静态变量来设置计时数字
static int num = 1;
QString s = formatTime(num);
ui->l4->setText(s);
num++;
});
案例2: 倒计时
cpp
ui->countDown->setText("同意(5)");
ui->countDown->setEnabled(false);
QTimer *cTimer = new QTimer(this);
cTimer->start(1000);
connect(cTimer, &QTimer::timeout, [=](){
timeNum--;
ui->countDown->setText(QString("同意(%1)").arg(timeNum));
if (timeNum == 0)
{
ui->countDown->setEnabled(true);
cTimer->stop();
}
});
案例3: 时钟
cpp
QTime t = QTime::currentTime();
ui->timeEdit->setTime(t);
QTimer *clock = new QTimer(this);
clock->start(1000);
connect(clock, &QTimer::timeout, [=](){
QTime t = QTime::currentTime();
ui->timeEdit->setTime(t);
});
案例4: 倒计时
cpp
// 获取开始时间戳, 即 当前时间戳
QDateTime startTime = QDateTime::currentDateTime();
qDebug() << startTime << startTime.toTime_t();
// 获取指定结束点时间戳
QDateTime endTime(QDate(2023, 10, 12), QTime(3, 0, 0));
qDebug() << endTime << endTime.toTime_t();
时间戳: 从 1970年1月1日 0点0分0秒开始,到某一个时间点的秒数。
unix 纪元, Linux --> 安卓、IOS
2024年5月21日 14:12:43 198493891382
剩余秒数 = 结束时间戳 - 当前时间戳
h = 剩余秒数 / 3600;
m = 剩余秒数 % 3600 / 60;
s = 剩余秒数 % 60;
7. 绘图
绘图也属于事件 paintEvent(QPaintEvent *event)
7.1 画家类
-
QPainter (画家类)用来绘图
-
常用方法:
-
drawLine() : 绘制直线
-
drawEllipse() :绘制椭圆
-
drawRect() :绘制矩形
-
drawText() :绘制文字
-
cpp
// 指定在当前窗口中画画
QPainter painter(this);
// 绘制直线
painter.drawLine(10, 10, 100, 100);
painter.drawLine(QPoint(500, 0), QPoint(400, 100));
// 绘制椭圆
painter.drawEllipse(100, 100, 50, 50);
painter.drawEllipse(QPoint(400, 100), 80, 40);
// 绘制矩形
painter.drawRect(50, 50, 100, 200);
// 绘制文字
painter.drawText(QPoint(200, 100), "今天天气不错");
7.2 画笔类
-
QPen(画笔类):用来设置画笔的风格,包括:颜色、线条样式、粗细等
-
常用方法:
-
QPen pen(QColor) : 设置画笔颜色
-
setWidth(int num) : 设置画笔粗细
-
setStyle(enum) : 设置画笔风格
-
painter.setPen(pen) : 让画家使用该画笔
-
cpp
// 设置画笔
QPen rPen(QColor(255, 0, 0));
rPen.setStyle(Qt::DashDotDotLine);
rPen.setWidth(5);
// 让画家使用该画笔
painter.setPen(rPen);
7.3 画刷
-
QBrush(画刷类): 可以为封闭图形填充颜色和样式
-
常用方法:
-
QBrush brush(QColor): 设置画刷的颜色
-
setStyle() : 设置样式
-
painter.setBrush() : 让画家使用该画刷
-
cpp
void Widget::paintEvent(QPaintEvent *event)
{
// 指定在当前窗口中画画
QPainter painter(this);
// 设置红色画笔
QPen rPen(QColor(255, 0, 0));
painter.setPen(rPen); // 让画家使用红色画笔绘图
// 绘制直线
painter.drawLine(10, 10, 100, 100);
painter.drawLine(QPoint(500, 0), QPoint(400, 100));
QPen gPen(QColor(0, 255, 0, 100));
gPen.setStyle(Qt::DashLine); // 设置线条为虚线
gPen.setWidth(3);
painter.setPen(gPen);
// 绘制椭圆
painter.drawEllipse(100, 100, 50, 50);
painter.drawEllipse(QPoint(400, 100), 80, 40);
QPen pen(QColor(180, 10, 89));
painter.setPen(pen);
// 设置画刷
QBrush brush(QColor(0, 0, 200));
brush.setStyle(Qt::Dense7Pattern); // 设置画刷的样式
painter.setBrush(brush); // 让画家使用该画刷
// 绘制矩形
painter.drawRect(50, 50, 100, 200);
// 绘制文字
painter.drawText(QPoint(200, 100), "今天天气不错");
}
7.4 绘制图片
QPixmap 用来绘制图片
绘制图片方法: painter.drawPixmap(QRect(), QPixmap());
cpp
painter.drawPixmap(QRect(100, 100, 200, 200), QPixmap(":/images/1.jpg"));
重新绘制图片使用 update() , 该方法属于 QWidget
cpp
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
this->resize(500, 300);
connect(ui->movePicBtn, &QPushButton::clicked, [=](){
this->posX += 10;
this->update();
});
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
if (this->posX > this->width())
{
this->posX = 0;
}
painter.drawPixmap(QRect(this->posX, 0, 200, 200), QPixmap(":/images/1.jpg"));
}
7.5 绘图设备
QPaintDevice: 绘图设备类是所有绘绘图类的祖先类
7.5.1 QPixmap
QPixmap 对不同的平台进行了显示的优化
示例1: 绘制图形并保存到硬盘
cpp
// 创建一个 300*300 的画布
QPixmap pix(300, 300);
// 为画布设置白色背景
pix.fill(Qt::white);
// 实例化画家类,并向画布上进行绘画
QPainter painter(&pix);
// 设置画家使用的画笔颜色
painter.setPen(QColor(12, 190, 88));
// 绘制图形
painter.drawLine(10, 10, 300, 300);
// 将绘制的图形保存成图片
pix.save("d:/123.jpg");
示例2:绘制图片
cpp
QPixmap pix;
if(pix.load(":/res/a.png"))
{
QPainter painter(this);
// 参数1: x坐标
// 参数2: y坐标
// 参数3: 宽度
// 参数4: 高度
painter.drawPixmap(0, 0, this->width(), this->height(), pix);
pix.load(":/res/b.png");
painter.drawPixmap(10, 30, pix.width() / 2, pix.height() / 2, pix);
}
else
{
qDebug() << "图片加载失败";
}
7.5.2 QImage
QImage 可以实现像素级操作
示例1: 使用 QImage 进行绘图
cpp
// 参数1 | 参数2: 画布的宽和高
// 参数3: 颜色格式
QImage img(300, 300, QImage::Format_RGB32);
// 画布背景色填充
img.fill(Qt::green);
// 让画家在画布上绘制
QPainter painter(&img);
painter.drawRect(QRect(0, 0, 200, 200));
// 保存图片
img.save("d:/222.png");
示例2: 绘图时修改像素点
cpp
void Widget::paintEvent(QPaintEvent *)
{
QImage img(":/images/1.jpg");
// img.load(":/images/1.jpg"); // 两种方式载入图片
QPainter painter(&img);
// 使用双循环绘制一个红色的矩形
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 80; j++)
{
QRgb value = qRgb(255, 0, 0);
img.setPixel(i, j, value);
}
}
painter.drawImage(0, 0, img);
}
7.5.3 QPicture
QPicture 可以记录和重现绘制 (能够对图片进行加密)
QPicture 绘制图片
cpp
void Widget::paintEvent(QPaintEvent *e)
{
QPicture pic;
QPainter painter;
// 开始绘制
painter.begin(&pic);
painter.drawEllipse(100, 100, 50, 50);
// 结束绘制
painter.end();
// 保存图片
pic.save("d:/333.abc");
}
读取自定义后缀的图片
cpp
void Widget::paintEvent(QPaintEvent *e)
{
QPicture pic;
QPainter painter(this);
pic.load("d:/123.abc");
painter.drawPicture(0, 0, pic);
}