欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 培训 > Qt-自定义控件鼠标事件键盘事件定时器绘图

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

2024/10/24 5:21:13 来源:https://blog.csdn.net/m0_74380601/article/details/142906053  浏览:    关键词:Qt-自定义控件鼠标事件键盘事件定时器绘图

1. 自定义控件

1.1 创建自定义控件

1.在项目目录上右键, 选择 "Add New"

2.选择 "Qt" --> "Qt 设计师界面类"  

 3.根据需求选择模板,此处选择空窗口

 4.设置类名 和 相关文件名

使用设计师界面类会产生三个文件:.h .cpp .ui

5.通过过ui文件设置控件

1694075445708

 6.在主窗口上设置 SoundWidget 控件

① 打开主窗口 ui 界面 (Widget.ui)

② 将 Widget 拖拽到主窗口中

③ 在 Widget 上点击右键,再选择 "提升为..."

 ④ 填写自定义控件类名,再点击提升

1.2 功能设置

  • 调整数字同时会影响水平滚动条

  • 调整水平滚动条同时影响数字

// 由于 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.再头文件中声明事件函数

#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 文件中实现事件

#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() : 该方法能够获取鼠标在显示中的位置坐标

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() 获取向上滚动 或者 向下滚动的角度

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 头文件中定义事件函数

void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);

MyInput 源文件中实现事件函数

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) : 关闭定时器

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 需要全局定义,放在 头文件 中

Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);// 启动定时器1timer1 = startTimer(1000);// 启动定时器2timer2 = 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方法能够停止定时器

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)在启动按钮上配置点击信号和槽

// 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: 倒计时

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: 时钟

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

// 获取开始时间戳, 即 当前时间戳
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() :绘制文字

// 指定在当前窗口中画画
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) : 让画家使用该画笔

// 设置画笔
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() : 让画家使用该画刷

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());

painter.drawPixmap(QRect(100, 100, 200, 200), QPixmap(":/images/1.jpg"));

重新绘制图片使用 update() , 该方法属于 QWidget

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: 绘制图形并保存到硬盘

// 创建一个 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:绘制图片

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 进行绘图

// 参数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: 绘图时修改像素点

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 绘制图片

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");
}

 读取自定义后缀的图片

void Widget::paintEvent(QPaintEvent *e)
{QPicture pic;QPainter painter(this);pic.load("d:/123.abc");painter.drawPicture(0, 0, pic);
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com