目录
Qt Event System
事件处理的方法
系统事件处理函数
基本事件
窗口显示事件
窗口关闭事件
窗口隐藏事件
窗口移动事件
窗口大小改变事件
窗口状态改变事件
鼠标事件
鼠标进入、离开事件
鼠标按下抬起事件
鼠标双击事件
鼠标移动事件
鼠标滚轮事件
示例:无边框窗口移动
键盘事件
键盘按下抬起事件
键盘事件实现按钮移动
焦点事件
右键菜单
右键菜单信号与槽方式
右键菜单事件方式
拖拽事件
输入法事件
定时器
定时器事件方式实现
QTimer信号与槽方式创建定时器
成员函数方式
单发
Qt Event System
在Qt中,事件是派生自抽象QEvent类的对象,它表示应用程序内发生的事情或应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件尤其相关(例如自定义的按钮Button类)。Qt程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件,当事件发生时,Qt将创建一个事件对象。
事件处理的方法
传递事件的通常方式是调用虚函数。例如,QPaintEvent通过调用QWidget::paintEvent()来传递。这个虚函数负责进行适当的响应,通常是重新绘制小部件。如果在虚函数的实现中不执行所有必要的工作,则可以调用基类的实现。
系统事件处理函数
基本事件
事件处理函数是重写父类或者基类的虚函数,不需要手动调用,会在事件循环中自动调用。
#禁用Qt6以前的函数(Cmake中)
add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x060000)
窗口显示事件
除隐藏和显示窗口外,窗口最小化会发送窗口隐藏事件,正常显示会发送窗口显示事件。
#include <QApplication>
#include <QWidget>
#include <QDebug>
#include <QMessageBox>
#include <QMoveEvent>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
resize(640,480);
}
~Widget(){}
// 窗口显示事件
void showEvent(QShowEvent* ev) override
{
QMessageBox::information(this, "提示", "窗口显示");
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
窗口关闭事件
当Qt从窗口系统接收到一个顶级小部件的窗口关闭请求时,将用给定的事件调用此事件处理程序。
默认情况下,接受事件并关闭小部件。您可以重新实现此函数,以更改小部件响应窗口关闭请求的方式。例如,您可以通过在所有事件上调用ignore()来防止窗口关闭。
主窗口应用程序通常使用该函数的重新实现来检查用户的工作是否已保存,并在关闭前请求权限。
void Widget::closeEvent(QCloseEvent* ev)override
{
auto ret = QMessageBox::question(this, "温馨提示", "你有未保存的操作,是否保存并关闭?");
if (ret == QMessageBox::StandardButton::Yes)
{
// 接收关闭
ev->accept();
// 与前一句代码效果相同 (设置为true表示接收该事件)
//ev->setAccepted(true);
}
else
{
// 不接收关闭
ev->ignore();
//ev->setAccepted(false);
}
}
窗口隐藏事件
void Widget::hideEvent(QHideEvent* ev)override
{
qInfo() << "我隐藏啦~";
}
窗口移动事件
// 窗口或控件移动事件
void moveEvent(QMoveEvent* ev) override
{
qInfo() << "移动事件: " << ev->oldPos() << ev->pos();
}
窗口大小改变事件
// 窗口大小改变事件
void resizeEvent(QResizeEvent* ev) override
{
qInfo() << "窗口大小改变: " << ev->oldSize() << ev->size();
}
窗口状态改变事件
如果需要检测程序中,某些东西是否发生了改变,可以通过void QWidget::changeEvent(QEvent *event)
来检测。
-
以下是常用事件:
-
QEvent::FontChange
-
QEvent::WindowTitleChange
-
QEvent::IconTextChange
-
QEvent::ModifiedChange
-
QEvent::MouseTrackingChange
-
QEvent::WindowStateChange
-
-
以
QEvent::WindowStateChange
窗口状态改变为例!
void Widget::changeEvent(QEvent* ev)override
{
if (ev->type() == QEvent::WindowStateChange)
{
auto we = static_cast<QWindowStateChangeEvent*>(ev);
qInfo() << "oldState" << we->oldState();
qInfo() << "curState" << this->windowState();
}
}
鼠标事件
鼠标进入、离开事件
#include <QApplication>
#include <QWidget>
#include <QDebug>
#include <QPushButton>
#include <QMouseEvent>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
resize(640,480);
btn = new QPushButton("Button", this);
//启用鼠标追踪
setMouseTracking(true);
}
~Widget(){}
//鼠标进入
void enterEvent(QEnterEvent* ev) override
{
//鼠标进入事件(进入客户区响应)
btn->setStyleSheet("background-color:red;");
}
//鼠标离开
void leaveEvent(QEvent* ev) override
{
//一般是重写按钮类,然后重写鼠标进入和离开事件
btn->setStyleSheet("background-color:green;");
}
private:
QPushButton* btn;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main1.moc"
鼠标按下抬起事件
//鼠标按下
void mousePressEvent(QMouseEvent* ev)override
{
//判断那个键按下
if(ev->button() == Qt::MouseButton::LeftButton)
{
//鼠标左键按下 按钮右移
btn->move(btn->x() + 10,btn->y());
//鼠标点击的坐标(局部的坐标)
btn->setText(QString("%1,%2")
.arg(ev->pos().x())
.arg(ev->pos().y()));
}
}
//鼠标抬起
void mouseReleaseEvent(QMouseEvent* ev) override
{
//判断什么键释放了
if(ev->button() == Qt::MouseButton::LeftButton)
{
btn->move(btn->pos()+QPoint(0,10));
}
}
鼠标双击事件
//鼠标双击
void mouseDoubleClickEvent(QMouseEvent* ev) override
{
qDebug() << __FUNCTION__;
}
鼠标移动事件
使用鼠标移动事件的时候需要启用鼠标追踪
//启用鼠标追踪
setMouseTracking(true);
//鼠标移动
void mouseMoveEvent(QMouseEvent* ev) override
{
//鼠标移动事件不会自动追踪鼠标,只有当有鼠标按键按下状态才会进行鼠标的追踪
qInfo() << __FUNCTION__;
//当启用鼠标追踪之后无需按下也可追踪鼠标
//获取鼠标按键(不能通过ev->button()获取)左右键均按下获取不到Qt::MouseButton::LeftButton
if(ev->buttons() == Qt::MouseButton::LeftButton) //buttons == button state
{
qInfo() << "鼠标左键按下并移动" << ev->buttons();
}
//这样,左右键均按下的情况也可以获取到
if(ev->buttons() & Qt::MouseButton::LeftButton)
{
qInfo() << ev->buttons();
}
}
没有启用鼠标追踪的情况下,鼠标不按下不能进行鼠标追踪
启用鼠标追踪后,不按下也能追踪鼠标的位置
鼠标滚轮事件
返回轮子旋转的相对量,单位为八分之一度。正值表示转轮向前旋转,远离用户;负值表示转轮向后向用户旋转。angleDelta().y()提供自上一个事件以来旋转普通垂直鼠标滚轮的角度。如果鼠标有水平滚轮,angleDelta().x()提供水平鼠标滚轮旋转的角度,否则就是0。有些鼠标允许用户倾斜滚轮来进行水平滚动,有些触摸板支持水平滚动手势;它也会出现在angleDelta().x()中。
大多数鼠标类型的工作步长为15度,在这种情况下,delta值是120的倍数;即120单位* 1/8 = 15度。
然而,有些鼠标的滚轮分辨率更高,发送的delta值小于120单位(小于15度)。为了支持这种可能性,可以累计添加来自事件的增量值,直到达到120的值,然后滚动小部件,或者可以部分滚动小部件以响应每个轮事件。但是为了提供更原生的感觉,您应该更喜欢在有pixelDelta()的平台上使用它。
//鼠标滚轮
void wheelEvent(QWheelEvent* ev) override
{
qInfo() << ev->angleDelta().x()/8 //横向滚动 120单位/8 = 15度
<< ev->angleDelta().y()/8; //纵向滚动 120单位/8 = 15度
}
示例:无边框窗口移动
在做窗口应用程序时,为了使窗口更简洁,有时候会将窗口setWindowFlag();属性设置为“Qt::WindowType::FramelessWindowHint”,这时,窗口将不可拖动,但我们可以通过以下代码尝试解决无边框窗体的拖动问题。
#include <QApplication>
#include <QWidget>
#include <QMouseEvent>
#include <QPushButton>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
resize(640,480);
//设置窗口无边框
setWindowFlag(Qt::WindowType::FramelessWindowHint);
auto btn = new QPushButton("关闭窗口", this);
connect(btn,&QPushButton::clicked,this,&Widget::close);
}
~Widget()
{
}
void mouseMoveEvent(QMouseEvent* ev) override
{
if(ev->buttons() & Qt::MouseButton::LeftButton)
{
QPoint pos = ev->pos();
//鼠标位置的全局坐标 - 鼠标按下的位置(局部坐标)
//move(mapToGlobal(pos) - pressPos);
move(ev->globalPosition().toPoint() - pressPos);
}
}
void mousePressEvent(QMouseEvent* ev) override
{
pressPos = ev->pos();
}
private:
QPoint pressPos; //保存鼠标按下的坐标
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
键盘事件
键盘按下抬起事件
// 基本使用 判断是什么按键按下(字符不区分大小写)不是字符消息
void keyPressEvent(QKeyEvent* ev) override
{
//如果ev没有使用,不想让编译器提示未使用的变量
Q_UNUSED(ev)
//当窗口中有上下左右键时,按钮中有控件(焦点在控件上按上下左右是没有反应的)
qInfo() << "keyPressEvent:" << Qt::Key(ev->key());
//返回此键生成的Unicode文本。不同平台按下Shift、Control、Alt和Meta等修饰键时的返回值不同,可能返回空字符串。
qInfo() << "text:" << ev->text();
//获取按下了什么键盘修饰符(shift、control、alt、meta(windows键)、keypad(小键盘,即数字键))
qInfo() << ev->modifiers();
}
//键盘按下事件
void keyPressEvent(QKeyEvent* ev) override
{
switch(ev->key())
{
case Qt::Key::Key_Up:
qInfo() << "up";
break;
default:
break;
}
//快捷键 Ctrl + A
if(ev->modifiers() & Qt::KeyboardModifier::ControlModifier &&
ev->key() == Qt::Key::Key_A)
{
qInfo() << "Ctrl + A";
}
//直接有判断是否有某个快捷键
if(ev->matches(QKeySequence::Copy))
{
qInfo() << "matches" << "Ctrl + C";
}
}
// 键盘抬起事件
void keyReleaseEvent(QKeyEvent* ev) override
{
switch(ev->key())
{
case Qt::Key::Key_Up:
qInfo() << "up release";
break;
default:
break;
}
}
//获取快捷键(显示)
for(int i = 0;i < 71; i++)
{
qInfo() << QKeySequence::StandardKey(i)
<< QKeySequence(QKeySequence::StandardKey(i)).toString();
}
//设置窗口关闭时销毁对象(而不是隐藏)
setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose);
键盘事件实现按钮移动
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
setWindowFlag(Qt::WindowType::FramelessWindowHint);
resize(640, 480);
button = new QPushButton("玩蛇", this);
// 设置按钮不可获取焦点(这样才能触发上下左右按键)
button->setFocusPolicy(Qt::FocusPolicy::NoFocus);
}
void keyPressEvent(QKeyEvent* ev)override
{
switch (ev->key())
{
case Qt::Key::Key_Up:
button->move(button->pos() + QPoint(0, -1));
break;
case Qt::Key::Key_Down:
button->move(button->pos() + QPoint(0, 1));
break;
case Qt::Key::Key_Left:
button->move(button->pos() + QPoint(-1, 0));
break;
case Qt::Key::Key_Right:
button->move(button->pos() + QPoint(1, 0));
break;
}
}
private:
QPushButton* button = nullptr;
};
焦点事件
这个事件处理程序可以在子类中重新实现,以接收小部件的键盘焦点事件(焦点接收)。
小部件通常必须将focuspolicy()设置为Qt::NoFocus以外的东西,以便接收焦点事件。(注意,程序员可以在任何小部件上调用setFocus(),即使是那些通常不接受焦点的小部件。)
默认实现更新小部件(不指定focusPolicy()的窗口除外)。
// 按tab键可以切换焦点
#include <QApplication>
#include <QWidget>
#include <QPushButton>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
resize(640,480);
for(int i = 0; i < 3; i++)
{
btns[i] = new QPushButton("Button",this);
btns[i]->move(i*100,i*100);
}
//为窗口设置焦点之后才可以触发焦点事件
this->setFocus();
}
~Widget()
{
}
//焦点进入事件
void focusInEvent(QFocusEvent* ev)override
{
qInfo() << "focus In";
btns[1]->setStyleSheet("border:3px solid red;");
}
//焦点退出事件
void focusOutEvent(QFocusEvent* ev)override
{
qInfo() << "focus Out";
btns[1]->setStyleSheet("border:3px solid green;");
}
private:
QPushButton* btns[3];
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
右键菜单
右键菜单信号与槽方式
// 信号方式
#include <QApplication>
#include <QWidget>
#include <QContextMenuEvent>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
resize(640,480);
// 设置上下文菜单选项
this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
connect(this, &Widget::customContextMenuRequested, this, &Widget::slot_contextMenu);
}
~Widget()
{
}
private slots:
void slot_contextMenu(const QPoint& pos)
{
qInfo() << "右键菜单弹出" << pos;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
右键菜单事件方式
// contextMenuEvent方式
void Widget::contextMenuEvent(QContextMenuEvent* ev)
{
// 直接在此处弹出菜单即可
qInfo() << "报告长官,请求弹出上下文菜单,也就是右键菜单!"
<< "请弹出在\n"
<< "全局坐标:"<<ev->globalPos()
<< "局部坐标:"<<ev->pos() << "位置";
}
拖拽事件
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QDragEnterEvent>
#include <QMimeData>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
resize(640,480);
//设置窗口接受拖拽
setAcceptDrops(true);
}
~Widget()
{
}
//拖拽事件
//使用时需要设置窗口接受拖拽
void dragEnterEvent(QDragEnterEvent* ev)override
{
const QMimeData* pData = ev->mimeData();
quint64 pos = pData->text().lastIndexOf(".");
QString str = pData->text().right(pData->text().size() - pos);
qInfo() << str;
if(str == ".doc")
//拖拽进入控件时调用
ev->acceptProposedAction(); //接受拖拽的建议操作
}
void dropEvent(QDropEvent* ev)override
{
//拖拽放下时调用
const QMimeData* pData = ev->mimeData();
qInfo() << pData->text(); //获取文件路径
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
输入法事件
// 输入法事件
void inputMethodEvent(QInputMethodEvent* ev)
{
qInfo() << ev->commitString() << ev->preeditString();
}
定时器
定时器事件方式实现
// 定时器事件实现定时器
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
//启动一个定时器,并返回定时器ID(可设置定时精度)
_timerID = startTimer(1000);
}
void timerEvent(QTimerEvent* ev)override
{
if (ev->timerId() == _timerID)
{
qInfo()<<_timerID<<"timeout";
}
}
private:
int _timerID;
};
QTimer信号与槽方式创建定时器
// 定时器的实现方式2(QTimer)
#include <QTimer>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
//创建定时器
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, [](){
qInfo() << "hello world";
});
// 定时器开始(一秒执行一次)
m_timer->start(1000);
}
private:
QTimer* m_timer;
};
成员函数方式
// 使用成员函数callOnTimeout代替connect
#include <QTimer>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
//创建定时器
m_timer = new QTimer(this);
m_timer->callOnTimeout(this, [](){
qInfo() << "hello world";
});
//m_timer->callOnTimeout([](){qInfo() << "Hello World!";});
// 定时器开始(一秒执行一次)
m_timer->start(1000);
}
private:
QTimer* m_timer;
};
单发
// QTimer静态成员函数(单次发射)
QTimer::singleShot(1000, [](){
qInfo() << "Hello World";
});