Qt事件系统 day7
事件系统
- 在Qt中,事件是派生自抽象QEvent类的对象,它表示应用程序内发生的事情或应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件尤其相关。
- Qt程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件,当事件发生时,Qt将创建一个事件对象。
- 当事件发生时,Qt通过构造适当的QEvent子类的实例来创建一个事件对象来表示它,并通过调用它的event()函数将它交付给QObject的一个特定实例(或它的一个子类)。
- 这个函数
event()
不处理事件本身;根据交付的事件类型,它为该特定类型的事件调用事件处理程序,并根据事件是被接受还是被忽略发送响应。
- 一些事件,如QMouseEvent和QKeyEvent,来自窗口系统;还有一些,比如QTimerEvent,来自其他来源;有些来自应用程序本身。
事件处理
- 传递事件的通常方式是调用虚函数
- 自定义处理了按钮控件左键单击,想要让父类也能够处理左键消息,可以把父类的mousePressEvent放在最后调用(不放在else中)
cpp
复制代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
class Button :public QPushButton
{
public:
Button(QWidget* parent = nullptr) :QPushButton(parent)
{
}
protected:
//重写了父类的虚函数,也就是不再使用父类的实现,由自己来实现
//但是按钮的点击信号,是在mousePressEvent函数里面触发的
void mousePressEvent(QMouseEvent* ev) override
{
if (ev->button() == Qt::MouseButton::LeftButton)
{
qDebug() << "按下小瓜";
}
//所以写完实现后,剩下的要交给父类处理
QPushButton::mousePressEvent(ev);
}
};
class Widget :public QWidget
{
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
{
auto btn = new Button(this);
btn->setText("小瓜");
connect(btn, &Button::clicked,this, []()
{
qDebug() << "小瓜瓜";
}
);
}
//处理鼠标点击事件,重写虚函数即可
void mousePressEvent(QMouseEvent* ev) override
{
//判断那个键按下
if (ev->button() == Qt::MouseButton::LeftButton)
{
//pos是程序的客户区坐标,globalPos是基于整个屏幕所在的坐标位
qDebug() << "leftButton Press" << ev->pos() << ev->globalPos();
}
}
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowIcon(QIcon(":/Resource/tubiao.ico"));
w.show();
return a.exec();
}
#include "main.moc"
- 运行结果
鼠标事件
cpp
复制代码
void CustomButton::mousePressEvent(QMouseEvent* ev)override
{
//获取鼠标按键 类型为枚举:Qt::MouseButton::
qDebug() << ev->button();
//如果同时有多个鼠标按键按下,需要判断左键是否按下
qDebug() << (ev->buttons() & Qt::MouseButton::LeftButton);
//获取鼠标坐标 position()返回浮点坐标,可以使用pos()获取整型坐标
qDebug() << ev->position(); //鼠标在本控件上的坐标
qDebug() << ev->scenePosition(); //鼠标在本控件所在的窗口位置
qDebug() << ev->globalPosition(); //鼠标相对于屏幕的坐标
//将其他按键传递给基类处理
QPushButton::mousePressEvent(ev);
}
cpp
复制代码
void CustomButton::mouseReleaseEvent(QMouseEvent*ev)override
{
QPushButton::mouseReleaseEvent(ev);
}
cpp
复制代码
void CustomButton::mouseDoubleClickEvent(QMouseEvent*ev)override
{
qDebug() << "小瓜";
}
- 鼠标移动
- 如果关闭了鼠标跟踪,则只有在移动鼠标时按下鼠标按钮时才会发生鼠标移动事件。如果打开了鼠标跟踪,即使没有按下鼠标按钮,也会发生鼠标移动事件。按钮是自动追踪鼠标的,但是QWidget是不会的,如果要给QWidget的子类重写鼠标移动,需要使用
void setMouseTracking(bool enable)
启用鼠标追踪。
cpp
复制代码
void CustomButton::mouseMoveEvent(QMouseEvent* ev)override
{
qDebug() << "鼠标移动的坐标位" << ev->pos();
}
- 鼠标滚轮
- 返回轮子旋转的相对量,单位为八分之一度。正值表示转轮向前旋转,远离用户;负值表示转轮向后向用户旋转。angleDelta().y()提供自上一个事件以来旋转普通垂直鼠标滚轮的角度。如果鼠标有水平滚轮,angleDelta().x()提供水平鼠标滚轮旋转的角度,否则就是0。有些鼠标允许用户倾斜滚轮来进行水平滚动,有些触摸板支持水平滚动手势;它也会出现在angleDelta().x()中。
- 大多数鼠标类型的工作步长为15度,在这种情况下,delta值是120的倍数;即120单位* 1/8 = 15度。
- 然而,有些鼠标的滚轮分辨率更高,发送的delta值小于120单位(小于15度)。为了支持这种可能性,可以累计添加来自事件的增量值,直到达到120的值,然后滚动小部件,或者可以部分滚动小部件以响应每个轮事件
cpp
复制代码
void CustomButton::wheelEvent(QWheelEvent* ev)override
{
//获取滚轮滚动方向
QPoint numDegrees = ev->angleDelta();
qDebug() <<"水平:" << numDegrees.x()/8 <<"垂直:" << numDegrees.y()/8;
}
cpp
复制代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
class Button :public QPushButton
{
public:
Button(QWidget* parent = nullptr) :QPushButton(parent)
{
}
protected:
//重写了父类的虚函数,也就是不再使用父类的实现,由自己来实现
//但是按钮的点击信号,是在mousePressEvent函数里面触发的
void mousePressEvent(QMouseEvent* ev) override
{
if (ev->button() == Qt::MouseButton::LeftButton)
{
qDebug() << "按下小瓜";
}
//所以写完实现后,剩下的要交给父类处理
QPushButton::mousePressEvent(ev);
}
};
class Widget :public QWidget
{
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
{
auto btn = new Button(this);
btn->setText("小瓜");
connect(btn, &Button::clicked,this, []()
{
qDebug() << "小瓜瓜";
}
);
//设置鼠标追踪,不需要的情况就不要用,会降低效率
//setMouseTracking(true);
}
//处理鼠标点击事件,重写虚函数即可
void mousePressEvent(QMouseEvent* ev) override
{
//判断那个键按下
if (ev->button() == Qt::MouseButton::LeftButton)
{
//pos是程序的客户区坐标,globalPos是基于整个屏幕所在的坐标位
qDebug() << "leftButton Press" << ev->pos() << ev->globalPos();
isPress = true;
}
}
//鼠标放开
void mouseReleaseEvent(QMouseEvent* ev) override
{
if (ev->button() == Qt::MouseButton::LeftButton)
{
isPress = false;
}
}
//移动鼠标
void mouseMoveEvent(QMouseEvent* ev) override
{
//鼠标左键按下,并且移动了鼠标,实现这个要开启鼠标追踪
if (isPress)
{
qDebug() << "左键按下,也移动的鼠标";
}
//buttons判断鼠标按下后还做了别的事情
if(ev->buttons() & Qt::MouseButton::RightButton)
{
qDebug() << "移动了鼠标并且按了右击";
}
}
//双击
void mouseDoubleClickEvent(QMouseEvent* ev) override
{
qDebug() << "双击";
}
//滚轮
void wheelEvent(QWheelEvent* ev) override
{
//判断滚轮的方向
qDebug() << ev->angleDelta().y() << ev->angleDelta().x();
}
private:
bool isPress = false;
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowIcon(QIcon(":/Resource/tubiao.ico"));
w.show();
return a.exec();
}
#include "main.moc"
- 运行结果
按键事件
cpp
复制代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
class Widget :public QWidget
{
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
{
//遍历Qt中的所有快捷键
for (size_t i = 0; i < 70; i++)
{
qDebug() << i << "-----" << QKeySequence::StandardKey(i) << "------"
<< QKeySequence::keyBindings(QKeySequence::StandardKey(i));
}
}
//按键事件
void keyPressEvent(QKeyEvent* ev) override
{
//当前是什么键按下
qDebug() << Qt::Key(ev->key());
//描述键 ctrl alt shift键等等
//ctrl+A
if (ev->modifiers() & Qt::KeyboardModifier::ControlModifier && ev->key() == Qt::Key_A)
{
qDebug() << "全选";
}
//matches:匹配
if (ev->matches(QKeySequence::StandardKey::Save))
{
qDebug() << "保存";
}
}
//按键放开事件
void keyReleaseEvent(QKeyEvent* ev) override
{
}
private:
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowIcon(QIcon(":/Resource/tubiao.ico"));
w.show();
return a.exec();
}
#include "main.moc"
- 运行结果
窗口关闭事件、大小改变、其他事件处理
- 窗口关闭
- 当Qt从窗口系统接收到一个顶级小部件的窗口关闭请求时,将用给定的事件调用此事件处理程序。
- 默认情况下,接受事件并关闭小部件。您可以重新实现此函数,以更改小部件响应窗口关闭请求的方式。例如,您可以通过在所有事件上调用ignore()来防止窗口关闭。
- 主窗口应用程序通常使用该函数的重新实现来检查用户的工作是否已保存,并在关闭前请求权限。
cpp
复制代码
void Widget::closeEvent(QCloseEvent* ev)override
{
auto ret = QMessageBox::question(this, "温馨提示", "你有未保存的操作,是否保存并关闭?");
if (ret == QMessageBox::StandardButton::Yes)
{
//保存并关闭
ev->accept();
//ev->setAccepted(true);
}
else
{
//不保存也不关闭
ev->ignore();
//ev->setAccepted(false);
}
}
- 窗口隐藏、显示
- 除隐藏和显示窗口外,窗口最小化会发送窗口隐藏事件,正常显示会发送窗口显示事件。
cpp
复制代码
void Widget::showEvent(QShowEvent* ev)override
{
qInfo() << "我显示啦~";
}
void Widget::hideEvent(QHideEvent* ev)override
{
qInfo() << "我隐藏啦~";
}
cpp
复制代码
void Widget::moveEvent(QMoveEvent* ev)override
{
qInfo() << "Widget moved" << "oldPos" << ev->oldPos() << "newPos" << ev->pos();
}
cpp
复制代码
void Widget::resizeEvent(QResizeEvent* ev)override
{
qInfo() << "Widget SizeChanged" << "oldSize" << ev->oldSize() << "newSize" << ev->size();
}
- 程序状态改变
- 如果需要检测程序中,某些东西是否发生了改变,可以通过
void QWidget::changeEvent(QEvent *event)
来检测。
- 以下是常用事件
- QEvent::FontChange
- QEvent::WindowTitleChange
- QEvent::IconTextChange
- QEvent::ModifiedChange
- QEvent::MouseTrackingChange
- QEvent::WindowStateChange
cpp
复制代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
#include <QMessageBox>
class Widget :public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
, btn(new QPushButton("更改标题", this))
{
resize(400, 400);
btn->setFixedSize(100, 100);
connect(btn, &QPushButton::clicked, this, [=]()
{
setWindowTitle("小瓜瓜");
}
);
}
//窗口关闭事件
void closeEvent(QCloseEvent* ev) override
{
auto ret = QMessageBox::question(this, "关闭窗口", "是否保存");
if (ret == QMessageBox::StandardButton::Yes)
{
//接收事件
ev->accept();
}
else
{
//忽略事件
ev->ignore();
}
}
//当窗口大小改变的时候,会调用
void resizeEvent(QResizeEvent* ev) override
{
//这样窗口大小改变的时候,按钮也会跟着变
btn->move(ev->size().width() - btn->width(), 0);
}
void changeEvent(QEvent* ev) override
{
switch (ev->type())
{
case QEvent::Type::WindowTitleChange:qDebug() << "改变标题" << this->windowTitle(); break;
}
}
private:
QPushButton* btn{};
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowIcon(QIcon(":/Resource/tubiao.ico"));
w.show();
return a.exec();
}
#include "main.moc"
- 运行结果
定时器
- 有两种定时器
- 定时器:周期性处理,因为在Qt中不能写死循环,不然会导致主程序的阻塞
- 第一种是QTimer
cpp
复制代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
#include <QMessageBox>
#include <QTimer>
class Widget :public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
{
//两种定时器
//1.QTimer
auto timer = new QTimer(this);
//最简单的调用,使用lambda表达式
/*timer->callOnTimeout([]()
{
qDebug() << "upData";
}
);*/
timer->callOnTimeout(this, &Widget::game_upData);
//发送信号到槽函数
connect(timer, &QTimer::timeout, this, &Widget::game_upData);
//开启定时器,60帧速度
timer->start(1000/60);
//QTimer的静态成员,这个示例在1000毫秒后,只会触发一次
QTimer::singleShot(1000, []()
{
qDebug() << "只触发一次";
}
);
}
void game_upData()
{
qDebug() << __FUNCTION__;
}
private:
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowIcon(QIcon(":/Resource/tubiao.ico"));
w.show();
return a.exec();
}
#include "main.moc"
- 运行结果
- 第二种定时器事件,只要在QOBject的子类里面,就可以重写定时器事件
cpp
复制代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
#include <QMessageBox>
#include <QTimer>
class Widget :public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
{
//开启定时器
timer_idOne = startTimer(500);
timer_idTow = startTimer(200);
}
//定时器事件
void timerEvent(QTimerEvent* ev) override
{
static int i = 0;
//当i为6的时候杀死定时器
if (i == 6)
{
killTimer(timer_idOne);
killTimer(timer_idTow);
//killTimer(ev->timerId());
}
if (ev->timerId() == timer_idOne)
{
qDebug() << timer_idOne;
}
else if (ev->timerId() == timer_idTow)
{
qDebug() << timer_idTow;
}
i++;
}
private:
//timer的id接收变量
int timer_idOne;
int timer_idTow;
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowIcon(QIcon(":/Resource/tubiao.ico"));
w.show();
return a.exec();
}
#include "main.moc"
- 运行结果
自定义事件的发送与处理
事件分发函数
- 传递事件通常方式是调用虚函数,如果在虚函实现中不执行必要的工作,则可能需要调用基类的实现
- 如果希望替换基类的事件处理函数,则必须自己实现所有的内容,实现自己所需的功能后,可以调用基类来获得不想处理的任何情况的默认行为
cpp
复制代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
#include <QMessageBox>
#include <QTimer>
class Widget :public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
{
}
//所有的事件处理函数都是从event()事件派发函数调用的
bool event(QEvent* ev) override
{
switch (ev->type())
{
//实现自己需要的事件操作
case QEvent::MouseButtonPress:mousePressEvent(dynamic_cast<QMouseEvent*>(ev)); break;
default:
break;
}
//最后交给父类去处理
return QWidget::event(ev);
}
void mousePressEvent(QMouseEvent* ev) override
{
qDebug() << ev->button();
}
private:
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowIcon(QIcon(":/Resource/tubiao.ico"));
w.show();
return a.exec();
}
#include "main.moc"
- 运行结果
发送事件
- 自定义事件创建与发送,通过构造合适的事件对象并进行使用
- sendEvent():立即处理事件。当它返回时,事件过滤器或对象本身已经处理了该事件,对于许多事件类。都有一个名为isAccept()的函数,它会告诉事件是被最后一个调用的处理程序接受还是拒绝
- postEvent():将事件发送到队列中,以便进行分派。它会分发所有发布的事件。
- 用户自定义的类型必须要在这个区间里
cpp
复制代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
#include <QMessageBox>
#include <QTimer>
//自定义事件
class CustomEvent :public QEvent
{
public:
enum Type { custom = QEvent::User };
CustomEvent(const QString& data) :QEvent(static_cast<QEvent::Type>(custom))
,m_data(data)
{
}
//析构函数
~CustomEvent()
{
qDebug() << __FUNCTION__;
}
//提供接口
QString data() const
{
return m_data;
}
protected:
QString m_data;
};
class Widget :public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
{
}
void mousePressEvent(QMouseEvent* ev) override
{
if (ev->button() == Qt::RightButton)
{
//向指定的对象发送事件
//1.sendEvent 发送栈区的事件,直到事件处理或完成之后,才会返回(表明sendEvent函数是阻塞的)
//如果申请内存放在堆区就不会自动释放,需要自己所手动释放
//CustomEvent ev("小瓜 sendEvetn customEvent");
//QApplication::sendEvent(this, &ev);
//2.postEvent 只能发送堆区的对象。一旦发送,直接返回,不需要等事件处理完成,一旦事件处理完成会自动释放
CustomEvent* ev = new CustomEvent("小瓜瓜");
QApplication::postEvent(this, ev);
}
}
//处理自定义事件函数
void customEvent(QEvent* ev) override
{
if (ev->type() == CustomEvent::custom)
{
auto cev = dynamic_cast<CustomEvent*>(ev);
if (cev)
{
qDebug() << cev->data();
}
}
}
private:
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowIcon(QIcon(":/Resource/tubiao.ico"));
w.show();
return a.exec();
}
#include "main.moc"
- 运行结果