QT-事件
事件处理过程
众所周知Qt是一个基于C++的框架,主要用来开发带窗口的应用程序(不带窗口的也行,但不是主流)。
我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率才是最高的)。所以在Qt框架内部为我们提供了一些列的事件处理机制,当窗口事件产生之后,事件会经过: 事件派发 -> 事件过滤->事件分发->事件处理 几个阶段。Qt窗口中对于产生的一系列事件都有默认的处理动作,如果我们有特殊需求就需要在合适的阶段重写事件的处理动作,比如信号与槽就是一种事件(event)是由系统或者 Qt 本身在不同的场景下发出的。当用户按下/移动鼠标、敲下键盘,或者是窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如鼠标/键盘事件等;另一些事件则是由系统自动发出,如计时器事件。每一个Qt应用程序都对应一个唯一的 QApplication 应用程序对象,然后调用这个对象的 exec() 函数,这样Qt框架内部的事件检测就开始了( 程序将进入事件循环来监听应用程序的事件 )。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow* w = new MainWindow;
w.show();
return a.exec();
}
事件在Qt中产生之后,的分发过程是这样的:
-
当事件产生之后,Qt使用用应用程序对象调用 notify() 函数将事件发送到指定的窗口:
[override virtual] bool QApplication::notify(QObject *receiver, QEvent *e);
-
事件在发送过程中可以通过事件过滤器进行过滤,默认不对任何产生的事件进行过滤。
// 需要先给窗口安装过滤器, 该事件才会触发
[virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)
- 当事件发送到指定窗口之后,窗口的事件分发器会对收到的事件进行分类:
[override virtual protected] bool QWidget::event(QEvent *event);
- 事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件。。。)分发给对应的事件处理器函数进行处理,每个事件处理器函数都有默认的处理动作(我们也可以重写这些事件处理器函数),比如:鼠标事件:
// 鼠标按下
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);
// 鼠标释放
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);
// 鼠标移动
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);
重写事件案例
程序关闭之前的询问,鼠标进入,鼠标离开,窗口大小改变
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMessageBox>
#include <QWheelEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::enterEvent(QEvent *event)
{
qDebug() << "mouse enter";
}
void Widget::leaveEvent(QEvent *event)
{
qDebug() << "mouse leave";
}
void Widget::wheelEvent(QWheelEvent *event)
{
qDebug() << event->angleDelta();
}
void Widget::closeEvent(QCloseEvent *event)
{
int ret = QMessageBox::warning(this, tr("My Application"),
tr("close the window\n"
"Do you want to close the window?"),
QMessageBox::Ok | QMessageBox::No
);
switch(ret){
case QMessageBox::Ok:
event->accept();
break;
case QMessageBox::No:
event->ignore();
break;
}
}
void Widget::resizeEvent(QResizeEvent *event)
{
qDebug() << "oldSize:" << event->oldSize()
<< "newSize:" << event->size();
}
void Widget::on_pushButton_clicked()
{
}
自定义按键
mybutton.h
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QWidget>
class MyButton : public QWidget
{
Q_OBJECT
private:
QPixmap pic;
public:
explicit MyButton(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;
void enterEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void clicked();
};
#endif // MYBUTTON_H
mybutton.cpp
#include "mybutton.h"
#include <QPainter>
MyButton::MyButton(QWidget *parent) : QWidget(parent)
{
pic.load(":/o1.png");
setFixedSize(pic.size());
update();
}
void MyButton::mousePressEvent(QMouseEvent *event)
{
pic.load(":/o3.png");
update();
emit clicked();
}
void MyButton::leaveEvent(QEvent *event)
{
pic.load(":/o1.png");
update();
}
void MyButton::enterEvent(QEvent *event)
{
pic.load(":/o2.png");
update();
}
void MyButton::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(rect(),pic);
}
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "mybutton.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->mybtn,&MyButton::clicked,[=](){
qDebug() << "myButton is clicked !";
});
}
Widget::~Widget()
{
delete ui;
}
事件方式实现字体放大缩小
自定义控件MyTextEdit
头文件
#ifndef MYTEXTEDIT_H
#define MYTEXTEDIT_H
#include <QTextEdit>
class MyTextEdit : public QTextEdit
{
public:
MyTextEdit(QWidget *parent);
protected:
void wheelEvent(QWheelEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void keyReleaseEvent(QKeyEvent *e) override;
private:
bool ctrlKeyPressed = 0;
};
#endif // MYTEXTEDIT_H
实现文件
#include "mytextedit.h"
#include <QWheelEvent>
#include <QDebug>
MyTextEdit::MyTextEdit(QWidget *parent) : QTextEdit(parent)
{
}
void MyTextEdit::wheelEvent(QWheelEvent *e)
{
//qDebug() << e->angleDelta().y();
if(ctrlKeyPressed == 1){
if(e->angleDelta().y() > 0){
zoomIn();
}else if(e->angleDelta().y() < 0){
zoomOut();
}
e->accept();
}else{
QTextEdit::wheelEvent(e);
}
}
void MyTextEdit::keyPressEvent(QKeyEvent *e)
{
if(e->key() == Qt::Key_Control){
// qDebug() << "ctrl Pressed";
ctrlKeyPressed = 1;
}
QTextEdit::keyPressEvent(e);
}
void MyTextEdit::keyReleaseEvent(QKeyEvent *e)
{
if(e->key() == Qt::Key_Control){
// qDebug() << "ctrl Release";
ctrlKeyPressed = 0;
}
QTextEdit::keyPressEvent(e);
}
事件过滤器
我们通过继承QTextEdit来重写事件实现Ctrl加滚轮的检测,还有一种处理方式,叫做事件过滤器
在Qt的事件处理过程中,引入事件过滤器(Event Filter)可以让你在事件达到目标对象之前进行拦截和
处理。这是一种强大的机制,允许你在不同对象间共享事件处理逻辑或在父对象中集中处理特定事件。
下面是加入事件过滤器的步骤:
- 定义事件过滤器: 事件过滤器通常是一个重写了 QObject::eventFilter() 方法的对象。这个方法
会在事件传递给目标对象之前被调用。
- 安装事件过滤器: 使用 QObject::installEventFilter() 方法安装事件过滤器。这个方法告诉Qt
在将事件发送给特定对象之前先通过过滤器对象。例如,如果你想在父窗口中过滤子窗口的事件,
你需要在父窗口的对象上调用 installEventFilter() ,并将子窗口作为参数传递。
- 事件过滤器逻辑: 在 eventFilter() 方法内部,你可以编写自定义逻辑来决定如何处理或忽略事
件。如果此方法返回 true ,则表示事件已被处理,不应该继续传递;如果返回 false ,则事件将
正常传递给目标对象。
- 事件分发: 当事件发生时,Qt首先将事件发送到安装了事件过滤器的对象。在这一步,
eventFilter() 方法被调用。
- 决定是否传递事件: 根据 eventFilter() 方法的返回值,Qt决定是否继续向目标对象传递事件。如
果过滤器返回 true ,事件处理到此结束;如果返回 false ,事件继续传递到原始目标对象。
- 目标对象处理事件: 如果事件过滤器允许事件继续传递,目标对象将像没有事件过滤器存在时那样处
理事件。
事件过滤器特别适用于以下情况:
当你想在不修改子类代码的情况下改变事件的行为。
当多个对象需要共享相同的事件处理逻辑。
当你需要在更高的层级上监控或修改应用程序的事件流。
通过使用事件过滤器,Qt应用程序可以获得更大的灵活性和更细粒度的事件处理控制。
鼠标滚轮和字体大小
ui->textEdit->installEventFilter(this); // 给textEdit安装了事件过滤器,为滚轮字体做准备
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Wheel) {
QWheelEvent *wheelEvent = dynamic_cast<QWheelEvent *>(event);
//检查键盘的CTRL是否被按下
if (QGuiApplication::keyboardModifiers() & Qt::ControlModifier){
if (wheelEvent->angleDelta().y() > 0) {
zoomInText(); // 滚轮向上滚动
} else {
zoomOutText(); // 滚轮向下滚动
}
}
return true;//表示事件已被处理
}
return QWidget::eventFilter(watched, event);
}
void Widget::zoomInText()
{
// 放大字体
QFont font = ui->textEdit->font();
font.setPointSize(font.pointSize() + 1);
ui->textEdit->setFont(font);
}
void Widget::zoomOutText()
{
// 缩小字体
QFont font = ui->textEdit->font();
font.setPointSize(font.pointSize() - 1);
ui->textEdit->setFont(font);
}
在 C++ 中,强制类型转换(或类型转换)是一种将变量从一种类型转换为另一种类型的方法。C++ 提供
了四种强制转换运算符,每种都有其特定的用途和适用场景:
- static_cast
static_cast 是最常用的类型转换运算符,用于无风险的转换,如整数到浮点数,字符到整
数等。
它在编译时执行,不执行运行时类型检查(RTTI)。
示例: int x = static_cast(y); 其中 y 可能是 float 类型。
- dynamic_cast
专门用于处理对象的多态性,只能用于指针和引用,且涉及对象类必须有虚函数。
它在运行时检查类型的安全性,如果转换失败,对于指针类型返回 nullptr ,对于引用类型
抛出异常。
示例: Derived *dp = dynamic_cast<Derived *>(bp); 其中 bp 是基类指针, Derived
是派生类。
- const_cast
用于修改类型的 const 或 volatile 属性。
通常用于去除对象的 const 性质,允许修改原本被声明为 const 的变量。
示例: const int a = 10; int* b = const_cast<int*>(&a);
- reinterpret_cast
用于进行低级别的重新解释转换,几乎无限制,但也是最危险的。
它可以将一种完全不相关的类型转换为另一种类型,比如将指针类型转换为整数类型。
示例: long p = reinterpret_cast(&object); 其中 object 是某个类的对象