Qt事件系统 day7
事件系统
- 在Qt中,事件是派生自抽象QEvent类的对象,它表示应用程序内发生的事情或应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件尤其相关。
- Qt程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件,当事件发生时,Qt将创建一个事件对象。
- 当事件发生时,Qt通过构造适当的QEvent子类的实例来创建一个事件对象来表示它,并通过调用它的event()函数将它交付给QObject的一个特定实例(或它的一个子类)。
- 这个函数
event()
不处理事件本身;根据交付的事件类型,它为该特定类型的事件调用事件处理程序,并根据事件是被接受还是被忽略发送响应。 - 一些事件,如QMouseEvent和QKeyEvent,来自窗口系统;还有一些,比如QTimerEvent,来自其他来源;有些来自应用程序本身。
事件处理
- 传递事件的通常方式是调用虚函数
- 自定义处理了按钮控件左键单击,想要让父类也能够处理左键消息,可以把父类的mousePressEvent放在最后调用(不放在else中)
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
class Button :public QPushButton
{
public:
Button(QWidget* parent = nullptr) :QPushButton(parent)
{
}
protected:
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)
{
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"
- 运行结果
鼠标事件
void CustomButton::mousePressEvent(QMouseEvent* ev)override
{
qDebug() << ev->button();
qDebug() << (ev->buttons() & Qt::MouseButton::LeftButton);
qDebug() << ev->position();
qDebug() << ev->scenePosition();
qDebug() << ev->globalPosition();
QPushButton::mousePressEvent(ev);
}
void CustomButton::mouseReleaseEvent(QMouseEvent*ev)override
{
QPushButton::mouseReleaseEvent(ev);
}
void CustomButton::mouseDoubleClickEvent(QMouseEvent*ev)override
{
qDebug() << "小瓜";
}
- 鼠标移动
- 如果关闭了鼠标跟踪,则只有在移动鼠标时按下鼠标按钮时才会发生鼠标移动事件。如果打开了鼠标跟踪,即使没有按下鼠标按钮,也会发生鼠标移动事件。按钮是自动追踪鼠标的,但是QWidget是不会的,如果要给QWidget的子类重写鼠标移动,需要使用
void setMouseTracking(bool enable)
启用鼠标追踪。
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的值,然后滚动小部件,或者可以部分滚动小部件以响应每个轮事件
void CustomButton::wheelEvent(QWheelEvent* ev)override
{
QPoint numDegrees = ev->angleDelta();
qDebug() <<"水平:" << numDegrees.x()/8 <<"垂直:" << numDegrees.y()/8;
}
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
class Button :public QPushButton
{
public:
Button(QWidget* parent = nullptr) :QPushButton(parent)
{
}
protected:
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)
{
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() << "左键按下,也移动的鼠标";
}
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"
- 运行结果
按键事件
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
class Widget :public QWidget
{
public:
Widget(QWidget* parent = nullptr) :QWidget(parent)
{
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());
if (ev->modifiers() & Qt::KeyboardModifier::ControlModifier && ev->key() == Qt::Key_A)
{
qDebug() << "全选";
}
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()来防止窗口关闭。
- 主窗口应用程序通常使用该函数的重新实现来检查用户的工作是否已保存,并在关闭前请求权限。
void Widget::closeEvent(QCloseEvent* ev)override
{
auto ret = QMessageBox::question(this, "温馨提示", "你有未保存的操作,是否保存并关闭?");
if (ret == QMessageBox::StandardButton::Yes)
{
ev->accept();
}
else
{
ev->ignore();
}
}
- 窗口隐藏、显示
- 除隐藏和显示窗口外,窗口最小化会发送窗口隐藏事件,正常显示会发送窗口显示事件。
void Widget::showEvent(QShowEvent* ev)override
{
qInfo() << "我显示啦~";
}
void Widget::hideEvent(QHideEvent* ev)override
{
qInfo() << "我隐藏啦~";
}
void Widget::moveEvent(QMoveEvent* ev)override
{
qInfo() << "Widget moved" << "oldPos" << ev->oldPos() << "newPos" << ev->pos();
}
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
#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
#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)
{
auto timer = new QTimer(this);
timer->callOnTimeout(this, &Widget::game_upData);
connect(timer, &QTimer::timeout, this, &Widget::game_upData);
timer->start(1000/60);
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的子类里面,就可以重写定时器事件
#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;
if (i == 6)
{
killTimer(timer_idOne);
killTimer(timer_idTow);
}
if (ev->timerId() == timer_idOne)
{
qDebug() << timer_idOne;
}
else if (ev->timerId() == timer_idTow)
{
qDebug() << timer_idTow;
}
i++;
}
private:
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"
- 运行结果
自定义事件的发送与处理
事件分发函数
- 传递事件通常方式是调用虚函数,如果在虚函实现中不执行必要的工作,则可能需要调用基类的实现
- 如果希望替换基类的事件处理函数,则必须自己实现所有的内容,实现自己所需的功能后,可以调用基类来获得不想处理的任何情况的默认行为
#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)
{
}
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():将事件发送到队列中,以便进行分派。它会分发所有发布的事件。
- 用户自定义的类型必须要在这个区间里
#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)
{
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"
- 运行结果