1. 什么是事件
2. 如何处理事件
- 所有事件对象类型都继承自抽象类QEvent, 鼠标事件QMouseEvent, 绘图事件QPaintEvent 都是 QEvent的子类
- 因为QWidget 继承了基类 QObject, 又覆盖了event()虚函数,所以当事件发生时会调用QWidget类中的事件处理函数。
- 所有的事件处理函数都是虚函数,可以被QWidget的子类覆盖。
- 可以继承QWidget或者其子类,QDialog、QMainWindow。然后在自定义的窗口子类中重写事件处理函数,当相应的事件被触发时,会利用多态的语法机制,所执行到的事件处理函数将是子类中重写的版本。
3. 绘图事件
3.1 案例:图片浏览器
3.1.1 构建好框架
右键按钮,点击Go slots , 可以直接在头文件和源文件中生成槽函数
这里自动生成的槽函数名是不可以改的
3.1.2 导入图片资源
把图片文件夹拷贝到工程目录下
右键工程名然后添加文件,再选择Qt资源
给prefix为/, 再add所有图片
3.1.3 代码示例
showimagedialog.h
#ifndef SHOWIMAGEDIALOG_H
#define SHOWIMAGEDIALOG_H
#include <QDialog>
#include <QPainter> // 画家类, 绘制图形
#include <QImage> // 图片类, 加载图片
namespace Ui {
class ShowImageDialog;
}
class ShowImageDialog : public QDialog
{
Q_OBJECT
public:
explicit ShowImageDialog(QWidget *parent = 0);
~ShowImageDialog();
private slots:
// prev按钮对应的槽函数
void on_m_btnPrev_clicked();
// next按钮对应的槽函数
void on_m_btnNext_clicked();
private:
// 绘图事件处理函数(), 虚函数
void paintEvent(QPaintEvent *);
private:
Ui::ShowImageDialog *ui;
int m_index; // 图片索引
};
#endif // SHOWIMAGEDIALOG_H
showimagedialog.cpp
#include "showimagedialog.h"
#include "ui_showimagedialog.h"
ShowImageDialog::ShowImageDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ShowImageDialog)
{
ui->setupUi(this);
m_index = 1;
}
ShowImageDialog::~ShowImageDialog()
{
delete ui;
}
void ShowImageDialog::on_m_btnPrev_clicked()
{
if(--m_index < 1){
m_index = 3;
}
update(); // 触发绘图事件
}
void ShowImageDialog::on_m_btnNext_clicked()
{
if(++m_index > 4){
m_index = 1;
}
update(); // 触发绘图事件
}
// 事件处理函数
void ShowImageDialog::paintEvent(QPaintEvent *){
// 1. 创建画家对象
QPainter painter(this);
// 2. 获取绘图矩形所在区域
QRect rect = ui->frame->frameRect();
// 3. 坐标平移,让rect和painter使用相同坐标系
rect.translate(ui->frame->pos());
// 4. 构建要绘制的图形对象
QImage image(":/image/"+ QString::number(m_index)+".jpg");
// 5. 使用painter将image图片画到rect
painter.drawImage(rect, image);
}
4. 定时器事件
- timeEvent是定时器事件处理函数,是一个虚函数,可以在QObject的子类中重写这个函数,达到自定义事件处理。为了满足虚函数覆盖条件,这个参数是不能少的。
4.1 案例:用 定时器事件 实现摇奖机
4.1.1 构建框架
4.1.2 导入图片资源
- 直接把图片问价夹 image 放入工程目录下
4.1.3 代码示例
erinedialog.h
#ifndef ERINEDIALOG_H
#define ERINEDIALOG_H
#include <QDialog>
#include <QTimer>
#include <QPainter>
#include <QDir>
#include <QTime>
#include <QVector>
#include <QImage>
#include <QDebug>
namespace Ui {
class ErineDialog;
}
class ErineDialog : public QDialog
{
Q_OBJECT
public:
explicit ErineDialog(QWidget *parent = 0);
~ErineDialog();
private slots:
// Start按钮对应的槽函数
void on_pushButton_clicked();
private:
// 加载图片到容器
void loadPhotos(const QString& path);
// 定时器事件处理函数(虚函数)
void timerEvent(QTimerEvent *);
// 绘图事件处理函数(虚函数)
void paintEvent(QPaintEvent *);
private:
Ui::ErineDialog *ui;
QVector<QImage>m_vecPhotos; // 保存图片的容器
int m_index; // 图片载容器索引
int m_timer; // 定时器ID
bool isStarted; // 标记:true正在摇奖,false停止摇奖
};
#endif // ERINEDIALOG_H
erinedialog.cpp
#include "erinedialog.h"
#include "ui_erinedialog.h"
ErineDialog::ErineDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ErineDialog)
{
ui->setupUi(this);
m_index = 0;
isStarted = false;
// 设置随机种子
qsrand(QTime::currentTime().msec());
// 加载"./image"中所有图片到容器
loadPhotos("./image");
qDebug() << "load image munber: " << m_vecPhotos.size();
}
ErineDialog::~ErineDialog()
{
delete ui;
}
// Start按钮对应的槽函数
void ErineDialog::on_pushButton_clicked()
{
if(isStarted == false){
isStarted = true; // 摇奖开始
m_timer = startTimer(50);
ui->pushButton->setText("Stop");
}
else{
isStarted = false; // 摇奖结束
killTimer(m_timer);
ui->pushButton->setText("Start");
}
}
void ErineDialog::loadPhotos(const QString& path)
{
// 遍历当前目录所有图片
// 创建一个目录对象
QDir dir(path);
// 获取目录下所有文件名
QStringList list1 = dir.entryList(QDir::Files);
for(int i = 0; i < list1.size(); i++){
QImage image(path + "/" + list1.at(i));
m_vecPhotos << image;
}
// 递归遍历所有子目录中的图片
// 获取目录下所有目录名,不要 . 和 ..
QStringList list2 = dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
for(int i=0; i<list2.size(); i++){
loadPhotos(path + "/" + list2.at(i));
}
}
// 定时器事件处理函数(虚函数)
void ErineDialog::timerEvent(QTimerEvent *){
m_index = qrand() % m_vecPhotos.size();
update();
}
// 绘图事件处理函数(虚函数)
void ErineDialog::paintEvent(QPaintEvent *){
// 1. 创建画家对象
QPainter painter(this);
// 2. 获取绘图矩形所在区域
QRect rect = ui->frame->frameRect();
// 3. 坐标平移,让rect和painter使用相同坐标系
rect.translate(ui->frame->pos());
// 4. 使用painter将image图片画到rect
painter.drawImage(rect, m_vecPhotos[m_index]);
}
4.2 案例: 用 定时器信号 实现摇奖机
- 把定时器的操作封装成成员函数的形式, 这种更灵活一点,每个Qtime对象就可以表达一个唯一的定时器,适合一个程序里有多个定时器的场景.
4.2.1 代码示例
erinedialog.h
#ifndef ERINEDIALOG_H
#define ERINEDIALOG_H
#include <QDialog>
#include <QTimer>
#include <QPainter>
#include <QDir>
#include <QTime>
#include <QVector>
#include <QImage>
#include <QDebug>
namespace Ui {
class ErineDialog;
}
class ErineDialog : public QDialog
{
Q_OBJECT
public:
explicit ErineDialog(QWidget *parent = 0);
~ErineDialog();
private slots:
// 定时器到时以后要执行的槽函数
void onTimeout(void);
// Start按钮对应的槽函数
void on_pushButton_clicked();
private:
// 加载图片到容器
void loadPhotos(const QString& path);
// // 定时器事件处理函数(虚函数)
// void timerEvent(QTimerEvent *);
// 绘图事件处理函数(虚函数)
void paintEvent(QPaintEvent *);
private:
Ui::ErineDialog *ui;
QVector<QImage>m_vecPhotos; // 保存图片的容器
int m_index; // 图片载容器索引
// int m_timer; // 定时器ID
QTimer m_timer; // 定时器
bool isStarted; // 标记:true正在摇奖,false停止摇奖
};
#endif // ERINEDIALOG_H
erinedialog.cpp
#include "erinedialog.h"
#include "ui_erinedialog.h"
ErineDialog::ErineDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ErineDialog)
{
ui->setupUi(this);
m_index = 0;
isStarted = false;
// 设置随机种子
qsrand(QTime::currentTime().msec());
// 加载"./image"中所有图片到容器
loadPhotos("./image");
qDebug() << "load image munber: " << m_vecPhotos.size();
//
connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
}
ErineDialog::~ErineDialog()
{
delete ui;
}
// Start按钮对应的槽函数
void ErineDialog::on_pushButton_clicked()
{
if(isStarted == false){
isStarted = true; // 摇奖开始
// m_timer = startTimer(50);
m_timer.start(50);
ui->pushButton->setText("Stop");
}
else{
isStarted = false; // 摇奖结束
// killTimer(m_timer);
m_timer.stop();
ui->pushButton->setText("Start");
}
}
void ErineDialog::loadPhotos(const QString& path)
{
// 遍历当前目录所有图片
// 创建一个目录对象
QDir dir(path);
// 获取目录下所有文件名
QStringList list1 = dir.entryList(QDir::Files);
for(int i = 0; i < list1.size(); i++){
QImage image(path + "/" + list1.at(i));
m_vecPhotos << image;
}
// 递归遍历所有子目录中的图片
// 获取目录下所有目录名,不要 . 和 ..
QStringList list2 = dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
for(int i=0; i<list2.size(); i++){
loadPhotos(path + "/" + list2.at(i));
}
}
//void ErineDialog::timerEvent(QTimerEvent *){
// m_index = qrand() % m_vecPhotos.size();
// update();
//}
// 定时器到时以后要执行的槽函数
void ErineDialog::onTimeout(void){
m_index = qrand() % m_vecPhotos.size();
update();
}
// 绘图事件处理函数(虚函数)
void ErineDialog::paintEvent(QPaintEvent *){
// 1. 创建画家对象
QPainter painter(this);
// 2. 获取绘图矩形所在区域
QRect rect = ui->frame->frameRect();
// 3. 坐标平移,让rect和painter使用相同坐标系
rect.translate(ui->frame->pos());
// 4. 使用painter将image图片画到rect
painter.drawImage(rect, m_vecPhotos[m_index]);
}
5. 鼠标事件
5.1 案例:实现鼠标左键拖拽label方块移动
5.1.1 构建框架
- styleSheet选择label颜色
5.1.2 代码示例
QRect(x, y, w, h), 描述一个矩形区域,有 位置 长宽
QPoint(x, y) 描述一个像素点,只有位置,支持加减法运算
QSize(w, h) 描述一个区域大小
mousedialog.h
#ifndef MOUSEDIALOG_H
#define MOUSEDIALOG_H
#include <QDialog>
#include <QMouseEvent> //鼠标事件类
namespace Ui {
class MouseDialog;
}
class MouseDialog : public QDialog
{
Q_OBJECT
public:
explicit MouseDialog(QWidget *parent = 0);
~MouseDialog();
private:
// 按下
void mousePressEvent(QMouseEvent *);
// 抬起
void mouseReleaseEvent(QMouseEvent *);
// 移动
void mouseMoveEvent(QMouseEvent *);
private:
Ui::MouseDialog *ui;
// 拖拽标记,鼠标再label内,并且左键点下时,标记为true
bool m_drag;
// m_pose 记录鼠标点下去那一刻,鼠标和labe的相对位置(这里控件的位置是控件左上角的位置)
QPoint m_pos;
};
#endif // MOUSEDIALOG_H
mousedialog.cpp
#include "mousedialog.h"
#include "ui_mousedialog.h"
MouseDialog::MouseDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::MouseDialog)
{
ui->setupUi(this);
}
MouseDialog::~MouseDialog()
{
delete ui;
}
// 按下, 通过event来知道当前是什么鼠标事件
void MouseDialog::mousePressEvent(QMouseEvent *event)
{
// 是否鼠标左键
if(event->button() == Qt::LeftButton){
// 获取label所在矩形区域
// rect对象描述的位置的坐标圆心应该是label控件的左上角,
// 而鼠标点击时所在的坐标圆心应该是窗口的左上角
// 为了保持一致,让rect的和当前窗口保持相同的坐标系, 要对rect的坐标进行平移。
QRect rect = ui->label->frameRect();
// 坐标平移,让rect和鼠标使用相同的坐标系
rect.translate(ui->label->pos());
// 判断鼠标点击的位置是否在rect矩形区域当中
if(rect.contains(event->pos())){
// 可以拖拽
m_drag = true;
// 获取鼠标和label的相对位置
m_pos = ui->label->pos() - event->pos();
}
}
}
// 抬起
void MouseDialog::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton){
// 左键抬起就不能拖拽了
m_drag = false;
}
}
//
void MouseDialog::mouseMoveEvent(QMouseEvent *event)
{
if(m_drag){
// 计算label要移动的位置
QPoint newPos = event->pos() + m_pos;
QSize s1 = size(); // 父窗口位置
QSize s2 = ui->label->size(); // 获取label位置
//x∈(0, 窗口宽-label宽)
if(newPos.x() < 0){
newPos.setX(0);
}
else if(newPos.x() > s1.width() - s2.width()){
newPos.setX(s1.width() - s2.width());
}
//y∈(0, 窗口高-label高)
if(newPos.y() < 0){
newPos.setY(0);
}
else if(newPos.y() > s1.height() - s2.height()){
newPos.setY(s1.height() - s2.height());
}
// 移动label到新位置
ui->label->move(newPos);
}
}
6. 键盘事件
6.1 案例:键盘方向键控制label方块移动
6.1.1 代码示例
keyboarddialog.h
#ifndef KEYBOARDDIALOG_H
#define KEYBOARDDIALOG_H
#include <QDialog>
#include <QKeyEvent> // 键盘事件类
namespace Ui {
class KeyboardDialog;
}
class KeyboardDialog : public QDialog
{
Q_OBJECT
public:
explicit KeyboardDialog(QWidget *parent = 0);
~KeyboardDialog();
// 按下
void keyPressEvent(QKeyEvent*);
private:
Ui::KeyboardDialog *ui;
};
#endif // KEYBOARDDIALOG_H
keyboarddialog.cpp
#include "keyboarddialog.h"
#include "ui_keyboarddialog.h"
KeyboardDialog::KeyboardDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::KeyboardDialog)
{
ui->setupUi(this);
}
KeyboardDialog::~KeyboardDialog()
{
delete ui;
}
// 按下,通过event知道当前是那个按键
void KeyboardDialog::keyPressEvent(QKeyEvent* event){
int x = ui->label->pos().x();
int y = ui->label->pos().y();
if(event->key() == Qt::Key_Up){
ui->label->move(x, y-10);
}
else if(event->key() == Qt::Key_Down){
ui->label->move(x, y+10);
}
else if(event->key() == Qt::Key_Left){
ui->label->move(x-10, y);
}
else if(event->key() == Qt::Key_Right){
ui->label->move(x+10, y);
}
}