Qt-自定义控件&鼠标事件&键盘事件&定时器&绘图

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: 计时器

实现步骤:

  1. 页面布局: 一个 QLabel 用来显示时间, 一个 QPushButton 用来控制计时器的启动和停止

  2. 设置定时器(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);
}
相关推荐
苏三说技术2 分钟前
Redis 性能优化的18招
数据库·redis·性能优化
单片机学习之路4 分钟前
【C语言】结构
c语言·开发语言·stm32·单片机·51单片机
ALISHENGYA10 分钟前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战项目二)
数据结构·c++·算法
阿雄不会写代码11 分钟前
ubuntu安装nginx
linux·服务器·网络
蜗牛hb13 分钟前
VMware Workstation虚拟机网络模式
开发语言·学习·php
arong_xu24 分钟前
现代C++锁介绍
c++·多线程·mutex
Tttian62227 分钟前
基于Pycharm与数据库的新闻管理系统(2)Redis
数据库·redis·pycharm
汤姆和杰瑞在瑞士吃糯米粑粑29 分钟前
【C++学习篇】AVL树
开发语言·c++·学习
Biomamba生信基地36 分钟前
R语言基础| 功效分析
开发语言·python·r语言·医药
DARLING Zero two♡36 分钟前
【优选算法】Pointer-Slice:双指针的算法切片(下)
java·数据结构·c++·算法·leetcode