使用 Qt event事件:综合指南

Qt事件分发器

关于在 Qt 中处理事件你需要知道的一切。

Qt 有两个主要机制允许开发人员对应用程序中发生的事情做出反应。其中之一,更常见的是信号和插槽。另一种是使用事件。本指南的目的是全面介绍在 Qt 应用程序中交付和处理事件的不同技术。该指南非常实用,并为读者以正确的方式尝试 IDE 中的技术提供了足够的基础。希望读者已经对使用 Qt 有基本的了解。

内容

  1. 什么是事件
  2. 事件和信号/插槽
  3. 使用事件
  4. 事件传播
  5. 一个具体的例子
  6. 事件和事件类
  7. Qt 中处理事件的不同方法
  8. 重新实现 QObject::event()
  9. QObject 上的事件过滤器
  10. 在 QApplication 上安装事件过滤器
  11. 继承 QApplication 并实现 notify()
  12. 发送您自己的事件
  13. 概括

什么是事件

事件是 Qt C++ 应用程序中的对象,它们确实由 QEvent 类表示。例如,当有人单击按钮时有一个事件:QMouseEvent,当您的一个小部件调整大小时的事件 QResizeEvent,当您的应用程序关闭时的事件 QCloseEvent 等等。

它们是一种低级机制,允许您控制类和对象(尤其是小部件)的外观和行为。它们对于在您的对象中构建与事件相关的功能非常有用,您不想依赖应用程序中的其他对象。例如,如果我希望我的按钮在鼠标悬停在它上面时变为绿色,您可以使用事件来做到这一点,并且该行为将不依赖于与我们的按钮一起使用的任何其他对象。

事件既可以从应用程序内部生成,也可以作为某些外部活动的结果。当一个事件发生时,Qt 构造一个适当的 QEvent 子类实例。Qt 通过调用目标对象(主要是小部件)的 event() 方法来传递事件。event() 方法不处理事件本身。它查看相关事件的类型 (Event::Type),并调用最合适的事件处理程序 (mousePressEvent、mouseMoveEvent、keyPressEvent,…) 并根据事件是被接受还是被忽略返回 true 或 false。我知道有些事情现在还不清楚,但对我来说,随着我们的前进,我们将解释更多,在指南结束时,大部分内容都会变得有意义。

事件和信号/插槽

事件的目的与信号和插槽的目的有些相同。毕竟,信号/插槽允许您在发生某些事情时做出响应。这是它的工作原理。发生了一些事情,发出了一个信号,如果对那个信号感兴趣,你可以将它连接到你的 slot 并在你的 slot 实现中做出你想要的响应。

事件的不同之处在于它们允许您执行更多低级的事情,这些事情会深深地影响您的对象的行为。如果深入了解,Qt 中的信号和槽机制本身就是由事件驱动的。这是正确的 !例如,QPushButton 的 clicked() 信号在 QPushButton 类的某些鼠标事件的实现深处发出。我个人熟悉的事件最流行的用途是在构建完全自定义的小部件或深度自定义现有小部件时。

使用事件

您可以在 Qt 应用程序中使用不同的方式来使用事件。我们将首先向您展示如何玩弄 QWidget 类的事件。从那里,我们将探索更多可以在 Qt 应用程序中使用事件的方法。首先在 Qt Creator 中创建一个 Widgets 应用程序,我更喜欢使用 QWidget 作为我的父类,但 QMainWindow 也可以正常工作。您应该从您的小部件标题类开始,如下所示

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    Ui::Widget *ui;
};

如果您想捕获小部件的关闭事件,您可以将 closeEvent() 方法的覆盖添加到小部件,如下所示

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
protected:
    void closeEvent(QCloseEvent * event);
private:
    Ui::Widget *ui;
};

并在实现文件中实现如下图

void Widget::closeEvent(QCloseEvent *event)
{
     event->accept();
     qDebug() << "QCloseEvent : Widget closed";
}

就像这样,我们在 Widget 类中自己处理了关闭事件。如果您运行该应用程序,您将看到通常无聊的小部件。但是如果你点击顶部栏中的 X 图标来关闭窗口,你会看到你的小部件关闭了,但你会看到一条调试消息“QCloseEvent : Widget closed”,证明我们的事件处理程序确实是小部件关闭时调用。

这是实现事件的正常流程,

  • 子类化您感兴趣的 Qt 类
  • 覆盖您感兴趣的事件

如何知道您将覆盖的事件?我听到你问。那么你必须检查你感兴趣的类的 Qt 文档。例如,我们可以在 QWidget 类文档中找到 closeEvent、mousePressEvent、mouseReleaseEvent 等等。

在我们开始探索您可以在 QWidget 类上使用的许多其他事件之前,让我们谈谈我们在事件实现中的 event->accept() 行。当您像我们刚才那样处理事件时,您可以选择接受该事件或忽略该事件。当您接受事件时,通过在事件参数上调用 accept() 方法,您向 Qt 框架发出信号,您已经处理了该事件并且它不会尝试以任何其他方式处理它。

当您通过在事件参数上调用 ignore() 来忽略事件时,您是在告诉 Qt 框架您正在拒绝该事件,并且如果可能,它将尝试寻找其他方法来处理该事件。在我们的示例中,我们已经接受了该事件并且一切都按预期工作:当我们单击 X 图标时,小部件正在关闭。您还应该知道,如果您没有明确指定您接受如下事件

void Widget::closeEvent(QCloseEvent *event)
{
     qDebug() << "QCloseEvent : Widget closed";
}

默认情况下将接受该事件。这是要记住的关键信息。在我们尝试之前,我想挑战你思考如果我们忽略事件处理程序中的事件会发生什么。您可以通过在您的事件上调用 ignore() 来忽略该事件。

void Widget::closeEvent(QCloseEvent *event)
{
     event->ignore();
     qDebug() << "QCloseEvent : Widget closed";
}

试试这个并运行应用程序。小部件将出现,如果您单击 X 图标关闭小部件,则不会发生任何事情!您只会看到来自事件处理程序的调试输出,但在这种情况下 Qt 只会忽略该事件。如果您碰巧需要禁用小部件上的 X(关闭)图标,这是实现此目的的一种方法。

事件传播

我们刚刚看到的 closeEvent 行为,在事件参数上调用 ignore() 导致小部件关闭操作被取消是特殊的。在正常情况下,Qt 会尝试将事件沿父子关系链向上传播,直到找到愿意处理该事件的处理程序。如果未找到该处理程序,则该事件将被丢弃或完全忽略。一个很好的典型示例是响应键盘按键的 keyPressEvent() 处理程序。引用文档这个事件处理程序,对于事件事件,可以在子类中重新实现,以接收小部件的按键事件。小部件必须调用 setFocusPolicy() 以最初接受焦点并获得焦点才能接收按键事件。如果您重新实现此处理程序,那么在您不对键执行操作的情况下调用基类实现非常重要。如果用户按下 QKeySequence::Cancel 的键序列(通常是 Escape 键),默认实现会关闭弹出窗口小部件。否则,该事件将被忽略,以便小部件的父级可以解释它。请注意,QKeyEvent 以 isAccepted() == true 开头,因此您不需要调用 QKeyEvent::accept() – 如果您对键进行操作,则不要调用基类实现。(Qt 文档)

要处理 keyPressEvents,您必须继承您感兴趣的类并覆盖 keyPressEvent() 方法。这现在开始成为第二天性。

此事件特有的另一个关键信息是,您正在为其处理事件的小部件当前必须保持焦点。您可以通过单击内部来将焦点放在小部件上,例如像 QLineEdit 和 QTextEdit 这样的小部件,但您也可以通过调用其 setFocus() 方法以编程方式在小部件上执行此操作。这是与特定事件相关的行为示例,它强化了始终检查官方文档对您可能有兴趣处理的给定事件的说明的良好做法。

文档中引用的行还说如果用户按下 QKeySequence::Cancel 的键序列(通常是 Escape 键),默认实现会关闭弹出窗口小部件。否则,该事件将被忽略,以便小部件的父级可以解释它。 . (Qt 文档)

它清楚地表明 keyPressEvent 的父实现正在做一些事情。根据您要实现的目标,您可能对父实现必须提供的内容感兴趣,也可能不感兴趣。如果您想完全绕过父实现正在执行的操作,只需在覆盖的事件处理程序中执行您的操作,不要调用父实现。顺便说一句,你通过做这样的事情来调用父实现

QWidget::keyPressEvent(event);

例如,如果您的父类恰好是 QWidget,则通过传入您的事件参数。这就是事件传播的含义。事件可以并且在大多数情况下是从父级传播到子级,直到找到对事件感兴趣的对象。子类通过在其父类中调用相同的事件方法传播给父类。

事件传播的另一个支柱是知道何时以及如何使用您正在传递的事件的 accept() 和 ignore() 方法。如果一个对象或小部件检测到某个事件之前已经被某人处理过,它不会费心去处理它。这些方法只是事件对象的 setAccepted() 方法之上的便利,它设置了接受标志。在大多数情况下,您不需要显式调用accept(),因为事件是在您的事件处理程序中发送给您的,默认情况下该标志设置为true。

一个具体的例子

到目前为止,我们已经看到了很多关于事件的信息,所以现在是启动我们的 Qt Creator IDE 并更多地使用这些事件的好时机。打开 IDE 并创建一个新的 Widgets 项目。添加一个新类并将其命名为 MyLineEdit 。该类将继承 QLineEdit,因为我们想在 LineEdit 中键入文本时捕获 keyPressEvents。修改你的类的标题,如下所示

class MyLineEdit : public QLineEdit
{
    Q_OBJECT
public:
    explicit MyLineEdit(QWidget *parent = nullptr);
    void keyPressEvent(QKeyEvent *event);
};

和实现 CPP 文件如下所示

MyLineEdit::MyLineEdit(QWidget *parent) : QLineEdit(parent)
{
}
 
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{
    qDebug() << "MyLineEdit : keyPressEvent , key : " << event->text();
    QLineEdit::keyPressEvent(event);
}

您可以继续在您的小部件类中创建此类的实例以查看它,但我们现在要抵制诱惑。相反,创建一个新类并将其命名为 ChildLineEdit,修改其标题如下所示

#include "mylineedit.h"
class ChildLineEdit : public MyLineEdit
{
    Q_OBJECT
public:
    explicit ChildLineEdit(QWidget *parent = nullptr);
     void keyPressEvent(QKeyEvent *event);
 
};

及其实现 CPP 文件如下所示

ChildLineEdit::ChildLineEdit(QWidget *parent) : MyLineEdit(parent)
{
}
 
void ChildLineEdit::keyPressEvent(QKeyEvent *event)
{
    qDebug() << " ChildLineEdit,keyPressEvent , key : " << event->text();
    MyLineEdit::keyPressEvent(event);
}

该类继承了我们之前创建的 MyLineEdit 类,因此我们需要包含“mylineedit.h”头文件,如上面的头文件所示。现在您可以跳转到您的小部件类构造函数并创建 ChildLineEdit 的实例,如下所示

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    //Declare child Line Edit
    ChildLineEdit * childLineEdit = new ChildLineEdit(this);
    QVBoxLayout * layout = new QVBoxLayout(this);
    layout->addWidget(childLineEdit);
 
    setLayout(layout);
}

我们正在创建 ChildLineEdit 的实例并将当前小部件指定为父小部件。接下来,我们将其放入小部件的布局中。如果您运行该应用程序,它将如下所示

在 LineEdit 中键入一些内容,您将看到 ChildLineEdit 和 MyLineEdit 中的事件处理程序都被调用。你能说出一个理由吗?这是在行动中的事件传播。ChildWidget 正在处理 keyPressEvent 在该事件处理程序中执行某些操作:在这种情况下,我们只是打印一个调试语句,但您可以在其中轻松完成任何您想要的操作。在我们自定义的事情之后,我们正在调用 keyPressEvent 的父实现。

MyLineEdit::keyPressEvent(event);

通过传入我们的事件作为参数。这会导致调用来自 MyLineEdit 的事件处理程序,我们可以在上面的屏幕截图中看到它的输出。要玩一点,修改 ChildLineEdit::keyPressEvent() 如下所示

void ChildLineEdit::keyPressEvent(QKeyEvent *event)
{
    qDebug() << " ChildLineEdit,keyPressEvent , key : " << event->text();
    qDebug() << "Event accepted : " << event->isAccepted();
   MyLineEdit::keyPressEvent(event);
}

在第 4 行,我们打印出事件的接受标志,以查看默认情况下它是被接受 (true) 还是被忽略 (false)。运行应用程序,在 LineEdit 中输入一些内容,您将看到默认情况下它是正确的。

这意味着事件参数在事件处理程序中传递给您,就好像有人已经对其调用了 accept() 方法一样。因此,如果您只想在那里做自己的事情并将事件标记为已接受,则可以省略对 accept() 的调用。但是出于代码可读性的原因,将它放在那里是一种很好的做法。在父(MyLineEdit)实现中,我们可以调查事件到达那里的接受标志,但修改事件处理程序,如下所示

void MyLineEdit::keyPressEvent(QKeyEvent *event)
{
    qDebug() << "MyLineEdit : keyPressEvent , key : " << event->text();
    if( event->isAccepted()){
        qDebug() << "Event has been already handled";
    }else{
        qDebug() << "Event hasn't been handled yet";
    }
     QLineEdit::keyPressEvent(event);
}

运行应用程序,输出应该类似于

ChildLineEdit,keyPressEvent , key :  "d"
Event accepted :  true
MyLineEdit : keyPressEvent , key :  "d"
Event has been already handled

可以看出,该事件被标记为已处理。这会导致父处理程序不对给定事件采取任何操作。您可以返回 ChildLineEdit 并明确忽略该事件,如下所示

void ChildLineEdit::keyPressEvent(QKeyEvent *event)
{
    qDebug() << " ChildLineEdit,keyPressEvent , key : " << event->text();
    qDebug() << "Event accepted : " << event->isAccepted();
    event->ignore();
   MyLineEdit::keyPressEvent(event);
}

运行应用程序,输出将如下所示

ChildLineEdit,keyPressEvent , key :  "d"
Event accepted :  true
MyLineEdit : keyPressEvent , key :  "d"
Event hasn't been handled yet

这意味着事件在 ChildLineEdit 中被标记为忽​​略,这反映在 MyLineEdit 中。如果您想阻止事件一起传播到链上,您可以省略对父实现的调用。如果你注释掉对 MyLineEdit::keyPressEvent(event) 的调用,你会看到只有 ChildLineEdit::keyPressEvent 会被调用。

void ChildLineEdit::keyPressEvent(QKeyEvent *event)
{
    qDebug() << " ChildLineEdit,keyPressEvent , key : " << event->text();
    qDebug() << "Event accepted : " << event->isAccepted();
    event->ignore();
   //MyLineEdit::keyPressEvent(event);
}

但请注意这里的问题。当您在行编辑中键入内容时,您看不到里面的文本。这是因为我们没有调用 MyLineEdit::keyPressEvent(),而后者又调用了 QLineEdit::keyPressEvent()。事实证明 QLineEdit::keyPressEvent() 包含使该文本显示的实现代码。这表明在事件处理程序中调用父实现对于您的应用程序以您希望的方式工作至关重要。

事件和事件类

到目前为止,我们只讨论了一堆事件,让您首先掌握一些概念,但您应该知道,它们的整个世界供您玩。事件都是 QEvent 类的子类,每个子类都添加了新的字段和方法来帮助实现它的目的。例如,QResizeEvent 将包含小部件的旧大小和当前大小,QMouseEvent 将包含鼠标在屏幕上单击的位置等等。下面是一个小部件子类,其中包含更多事件供您使用。表头如下图

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
protected:
    void closeEvent(QCloseEvent * event);
    void contextMenuEvent(QContextMenuEvent * event);
    void enterEvent(QEvent * event);
    void leaveEvent(QEvent * event);
    void mousePressEvent(QMouseEvent * event);
    void mouseReleaseEvent(QMouseEvent * event);
    void mouseDoubleClickEvent(QMouseEvent * event);
    void mouseMoveEvent( QMouseEvent * event );
    void keyPressEvent( QKeyEvent * event );
    void wheelEvent( QWheelEvent * event );
    void resizeEvent(QResizeEvent * event);
    void paintEvent(QPaintEvent * event);
private:
    Ui::Widget *ui;
};

这是它的实现cpp文件

Widget::~Widget()
{
    delete ui;
}
 
void Widget::closeEvent(QCloseEvent *event)
{
//    event->accept();
//    event->ignore();
     qDebug() << "QCloseEvent : Widget closed";
}
 
void Widget::contextMenuEvent(QContextMenuEvent *event)
{
   qDebug() << "ContextMenu";
    event->accept();
    qDebug() << "QContextMenuEvent : Should pop up a context menu";
    qDebug() << "Event x :" << event->x() << " event y : " <<event->y();
   qDebug() << "Event reason : " << event->reason();
 
   event->ignore();
}
 
void Widget::enterEvent(QEvent *event)
{
    event->accept();
    qDebug() << "Mouse pointer entered widget space";
}
 
void Widget::leaveEvent(QEvent *event)
{
    event->accept();
    qDebug() << "Mouse pointer left widget space";
    releaseKeyboard();
}
 
void Widget::mousePressEvent(QMouseEvent *event)
{
    qDebug() << "Mouse pressed";
    qDebug() << "Button : " << event->button();
    grabKeyboard();
    event->accept();
}
 
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
    qDebug() << "Mouse released";
    releaseKeyboard();
    event->accept();
}
 
void Widget::mouseDoubleClickEvent(QMouseEvent *event)
{
    qDebug() << "Mouse double clicked";
    event->accept();
}
 
void Widget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug() << "Mouse moved to ("<<event->x() << "," << event->y() << ")";
    event->accept();
}
 
void Widget::keyPressEvent(QKeyEvent *event)
{
    qDebug() << "KeyPress event, pressed the key" << event->key();
    QString modifiers;
    if ( event->modifiers()&amp;Qt::ShiftModifier){
        modifiers += "Shift ";
    }
    if ( event->modifiers()&amp;Qt::ControlModifier){
        modifiers += "Control ";
    }
    if ( event->modifiers()&amp;Qt::AltModifier){
        modifiers += "Alt ";
    }
    qDebug() << "Modifiers : " << modifiers;
 
    //Detect Shift+A
    if ( event->modifiers() &amp; Qt::ShiftModifier){
        if(event->key() == 65){//
            qDebug() << "Shift A detected";
        }
        }
}
 
void Widget::wheelEvent(QWheelEvent *event)
{
 
    qDebug() << "Weel Event Delta : " << event->delta();
    qDebug() << " x : " << event->x() << ", y : " <<event->y();
    qDebug() << " Orientation : " << event->orientation();
 
}
 
void Widget::resizeEvent(QResizeEvent *event)
{
 
    qDebug() << "Widget resized , old size : " << event->oldSize();
    qDebug() << " new size : " << event->size();
}
 
void Widget::paintEvent(QPaintEvent *event)
{
    //Can be triggered for multiple reasons. Examples are when widget is resized or when an other maximized widget lies on top of this widget
    qDebug() << "Paint event. Rect is : " << event->rect();
 
}

实现文件可能包含我们尚未讨论的内容,但它们特定于给定事件,您可以查看官方文档以获取有关这些内容的更多详细信息。我只是想用我们尚未讨论的其他事件为您提供更多示例。

笔记: 不要只看这些事件。关闭 IDE 并与它们一起玩。

Qt 中处理事件的不同方法

到目前为止,我们只看到了一种在 Qt 中处理事件的方法:子类化您感兴趣的类并覆盖事件方法。这是最常见的方式,但也是最不强大的方式来完成这项工作。在本节中,您将了解处理事件的全貌。引用文档,有五种不同的方式可以处理事件;重新实现这个虚函数只是其中之一。下面列出了所有五种方法:

  1. 重新实现paintEvent()、mousePressEvent()等。这是最常见、最简单、最不强大的方法。
  2. 重新实现 QCoreApplication::notify。这是非常强大的,提供完全的控制;但一次只能激活一个子类。
  3. 在 QCoreApplication::instance() 上安装事件过滤器。这样的事件过滤器能够处理所有小部件的所有事件,因此它与重新实现 notify() 一样强大;此外,可以有多个应用程序全局事件过滤器。全局事件过滤器甚至可以看到禁用小部件的鼠标事件。请注意,应用程序事件过滤器仅对存在于主线程中的对象调用。
  4. 重新实现 QObject::event() (就像 QWidget 一样)。如果您这样做,您将获得 Tab 按键,并且您可以在任何特定于小部件的事件过滤器之前查看事件。
  5. 在对象上安装事件过滤器。这样的事件过滤器获取所有事件,包括 Tab 和 Shift+Tab 按键事件,只要它们不更改焦点小部件。

到目前为止,我们已经看到了 1 号,所以我们不会再谈论它了。

重新实现 QObject::event()

您可以通过子类化您感兴趣的类来使用此方法,但不是实现特定的事件处理程序,如 keyPressEvent() …,而是实现 QObject::event() 覆盖。这允许所有事件通过您的覆盖,您可以决定处理哪些事件以及将哪些事件向上引导事件传播链。为了玩这个,让我们创建一个 QPushButton 子类并实现我们自己的 event() 覆盖,如下面的标题所示

class MyButton : public QPushButton
{
    Q_OBJECT
public:
    explicit MyButton(QWidget *parent = nullptr);
signals:
protected:
    bool event(QEvent * event) override;
public slots:
};

实现 cpp 文件看起来像

MyButton::MyButton(QWidget *parent) : QPushButton(parent)
{
}
 
bool MyButton::event(QEvent *event)
{
    if( (event->type() == QEvent::MouseButtonPress)
            || (event->type() == QEvent::MouseButtonDblClick)){
        qDebug() << "MyButton::Event : Pressed the MyButton instance. Consuming event";
        return true;
    }
    //Remember to call the event method of the base class for the events that you don't handle
    return QPushButton::event(event);
}

您可能已经注意到 event() 方法返回 bool 。这里的逻辑与我们在 accept() 和 ignore() 方法中看到的非常相似。当您返回 true 时,您是在告诉 Qt 系统该事件已被处理,而返回的 false 意味着该事件已被忽略。

因为所有的事件都通过这个方法,所以你必须检查你收到了哪个特定的事件。您可以依靠 Event::Type enum 来做到这一点。从上面的代码中,您可以看到我们正在检测自定义按钮的鼠标单击和双击。如果是这些事件中的任何一个,我们将显示一个调试输出语句并返回 true 以表明该事件已被处理。请注意,您必须为您不处理的所有事件调用 event() 的父实现。否则,您最终会得到一个对其他事件没有响应的小部件。

如果出于某种原因您想通过一个地方引导所有事件,则此方法很好。同样如文档中所述,您可以按下 Tab 键,并且可以在任何特定于小部件的事件过滤器之前查看事件。

要尝试这一点,请在您的小部件类中创建一个按钮实例并将一个插槽连接到它

MyButton * myButton = new MyButton(this);
myButton->setText("MyButton");
connect(myButton,SIGNAL(clicked()),this,SLOT(myButtonClicked()));

运行应用程序,您将看到当您单击或双击按钮时,不会调用该插槽。相反,来自 MyButton::event() 的消息将出现

MyButton::Event : Pressed the MyButton instance. Consuming event

现在为什么连接到按钮的插槽没有被调用?这是因为连接到该插槽的信号: clicked() 没有被触发。该信号在 QPushButton 的事件句柄中的某处被触发,并且当我们检测到用户只是在我们的按钮上单击 (QEvent::MouseButtonPress) 或双击 (QEvent::MouseButtonDblClick) 时,我们已经绕过了 QPushButton 的任何事件处理。这也表明在调用任何特定事件处理程序(如 mousePress 或 keyPress)之前调用 event() 方法。

QObject 上的事件过滤器

事件过滤器是 QObject 类的子类,您可以将其附加到给定对象以在事件到达目标对象之前拦截事件。例如,您可以将过滤器附加到按钮,过滤器将在按钮之前获得鼠标按下事件。通过在对象上调用 QObject::installEventFilter() 来安装过滤器。事件过滤器在目标对象之前处理事件,允许它根据需要检查和丢弃事件。可以使用 QObject::removeEventFilter() 函数删除现有的事件过滤器。您在必须覆盖的 eventFilter() 方法中拦截事件。例如,假设我们想在有人输入行编辑时过滤掉数字。我们可以创建一个 DigitFilter 类,如下所示

class DigitFilter : public QObject
{
    Q_OBJECT
public:
    explicit DigitFilter(QObject *parent = nullptr,QString  msg = "");
protected:
bool eventFilter( QObject *dest, QEvent *event );
 
signals:
 
public slots:
private :
QString message;
};

实现可能类似于

DigitFilter::DigitFilter(QObject *parent,QString msg) : QObject(parent)
{
    message = msg;
}
 
bool DigitFilter::eventFilter(QObject *dest, QEvent *event)
{
    if( event->type() == QEvent::KeyPress )
    {
        qDebug() << "Event filter for " << message << " triggered";
        QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
        static QString digits = QString("1234567890");
        if( digits.indexOf( keyEvent->text() ) != -1 )
            //Returning true here signals that the event has been handled and it's not sent to the destination.
            return true;
    }
    //This sends the event to be handled by the filter of the base class or the event handlers of the base class themselves
    return QObject::eventFilter(dest, event);
}

事件过滤器还将获取安装它的对象的所有事件。一旦进入 eventFilter() 方法,我们必须检查我们感兴趣的事件。在这种情况下,KeyPress 事件。之后,我们必须检查按下了哪个键,以了解它是否是数字。如果我们返回 true 表示我们不想再处理这个事件,换句话说,事件过滤器已经在目标对象(安装它的地方)上完成了这个事件需要做的所有事情,没有其他人事件传播链的上游应该担心它。这会导致当您在我们要安装过滤器的行编辑中键入数字时,不会显示或处理数字。假设您在 Qt Creator 中创建并打开了一个简单的小部件项目,将 DigitFilter 类添加到项目中,并将行编辑组件拖到小部件表单中。在wiget构造函数中,创建一个过滤器并安装在行edit上如下图

DigitFilter* filter = new DigitFilter(this," line edit");
ui->lineEdit->installEventFilter(filter);
ui->lineEdit->setText("line edit");

这将导致行编辑的所有事件都通过过滤器对象的 eventFilter() 方法。过滤器将过滤掉数字,当您运行应用程序时,您不会在用户界面中看到这些数字。运行应用程序并尝试一下!当您不想弄乱目标对象并且只想通过事件影响它的行为时,事件过滤器非常有用。

在 QApplication 上安装事件过滤器

除了在常规 QObject 上安装过滤器外,您还可以将它们安装在应用程序中的单个 QApplication 实例上。显然,QApplication 上的事件过滤器在应用程序中任何其他对象上安装的任何事件过滤器之前被调用。让我们试试这个。假设您在 IDE 中创建了一个简单的小部件应用程序,创建一个新的过滤器类并将其命名为 MFilter

class MFilter : public QObject
{
    Q_OBJECT
public:
    explicit MFilter(QString message,QObject *parent = nullptr);
protected:
    bool eventFilter( QObject *dest, QEvent *event );
signals:
public slots:
private:
    QString m_message;
};

就像我们之前看到的那样,它只是一个常规过滤器。其实现如下图

MFilter::MFilter(QString message,QObject *parent) : QObject(parent),
    m_message(message)
{
}
 
bool MFilter::eventFilter(QObject *dest, QEvent *event)
{
    if( event->type() == QEvent::MouseButtonPress
            || event->type() == QEvent::MouseButtonDblClick){
        qDebug() << "Event hijacked " << m_message;
        return true; //Event handled here. No need to propagate
    }
    return QObject::eventFilter(dest,event);
}

我希望很清楚我们正在过滤鼠标点击和双击。现在您可以疯狂地全面扩展,并在您的 main 函数中的 QApplication 实例上安装此过滤器

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
 
    Widget w;
 
    MFilter * filter = new MFilter("FromMain",&amp;w);
    a.installEventFilter(filter);
    w.show();
 
    return a.exec();
}

如果您在表单中创建按钮并附加插槽以在单击它们时做出响应,如下所示

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    MFilter * filter = new MFilter("FromWidget",this);
    ui->button1->installEventFilter(filter);
 
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::on_button1_clicked()
{
    qDebug() << "Clicked on button1";
}
 
void Widget::on_button2_clicked()
{
    qDebug() << "Clicked on button2";
}

并运行应用程序,您将看到当您单击按钮时,插槽不会响应,而是会在下面的调试输出消息中看到过滤器响应

Event hijacked  "FromMain"

继承 QApplication 并实现 notify()

QApplication::notify() 是 Qt 调用以将事件发送到接收者的方法。在您的 Qt 应用程序中使用 QApplication 的子类并覆盖虚拟方法 notify() 是迄今为止拦截事件并处理它们的最强大的方法。你使用它的方式与我们在 QObject::event() 和 QObject::eventFilter 中看到的非常相似。您将 QApplication 子类化

class Application : public QApplication
{
    Q_OBJECT
public:
    explicit Application(int &amp;argc, char **argv);
protected:
     bool notify(QObject *receiver, QEvent *event);
signals:
public slots:
};

并覆盖 notify() 方法,如下所示

Application::Application(int &amp;argc, char **argv) : QApplication(argc,argv)
{
}
 
bool Application::notify(QObject *receiver, QEvent *event)
{
    Q_UNUSED(receiver);
    if ( event->type() == QEvent::MouseButtonPress
         || event->type() == QEvent::MouseButtonDblClick){
        qDebug() << "Notify method of Application called";
        return true;
    }
   return QApplication::notify(receiver,event);
}

同样,我们只是过滤掉鼠标点击和双击。您可以在使用常规 QApplication 类的地方使用 QApplication 的这个子类。

int main(int argc, char *argv[])
{
    Application a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

要尝试这一点,您可以在小部件应用程序中创建按钮,将信号从按钮连接到插槽并调试插槽中的输出。如果您运行应用程序,插槽不会响应按钮单击,因为单击和双击在我们的 Application::notify() 覆盖中被过滤掉了。

从我们目前所见,您有望掌握 Qt 在事件处理方面的灵活性。就我个人而言,它感觉就像一条流动的河流,为您提供不同的出口点,您可以通过这些出口点做自己的事情。

然而,有了所有这些选项,问题就出现了,应该在他们的应用程序中使用哪种方法。我的策略是从低口径武器开始,只在必要时升级到更重的武器。我通常做的只是继承感兴趣的类和特定的事件处理程序。我也经常使用过滤器,尤其是当我精心设计出一个可以在许多小部件或对象上使用的过滤器时。但这是我个人的经历。我敢肯定,随着您越来越多地使用 Qt GUI,您将在使用事件时养成自己的习惯。

发送您自己的事件

我们刚刚看到的处理事件的五种方法是您在响应 Qt 应用程序中的事件时主要选择的方法。但是,在文档中,您大部分时间会遇到 postEvent() 和 sendEvent() 方法,它们用于将您自己的事件发送到 Qt 应用程序中的目标对象。因此,为了完整起见,我们将对它们进行一些探索。

sendEvent 的文档非常清楚,它只是立即将事件发送到目标。postEvent() 的工作方式略有不同,它将事件添加到某个定义的队列中,队列中的所有事件稍后在 Qt 决定的时间处理。每个都有其优点和缺点。请注意,您很少需要直接调用这些方法,一方面,作为开发人员,您没有太多机会生成您想要发送到某处的事件,事件主要由以下用户生成您的应用程序(点击,…)或窗口系统。第二个原因是即使您需要生成事件,您通常也会通过 Qt 定义的方法来完成,例如 update() 和 repaint() 。如果您好奇,我们将尝试发送我们自己的事件。

创建一个常规的裸骨小部件应用程序。添加一个新的 QPushButton 子类,您将为其覆盖 mousePressEvent、mouseMoveEvent 和 mouseReleaseEvent。标题如下所示

class Button : public QPushButton
{
    Q_OBJECT
public:
    explicit Button(QWidget *parent = nullptr);
protected:
    void mouseMoveEvent(QMouseEvent * e);
    void mousePressEvent(QMouseEvent *e);
    void mouseReleaseEvent(QMouseEvent *e);
signals:
public slots:
};

以及下面的实现

Button::Button(QWidget *parent) : QPushButton(parent)
{
}
 
void Button::mouseMoveEvent(QMouseEvent *e)
{
    qDebug() << "Mouse move at " << e->pos() ;
    QPushButton::mouseMoveEvent(e);
}
 
void Button::mousePressEvent(QMouseEvent *e)
{
    qDebug() << "Mouse press at " << e->pos() ;
    QPushButton::mousePressEvent(e);
}
 
void Button::mouseReleaseEvent(QMouseEvent *e)
{
    qDebug() << "Mouse release at " << e->pos() ;
    QPushButton::mouseReleaseEvent(e);
}

我们的意图是在我们的用户界面中创建两个按钮,button1 和 button2。在 button1 的插槽中,我们将制作一个 MouseEvent 并将其发送给 button2。换句话说,当您单击按钮 1 时,看起来就像您正在单击按钮 2。

为了让我们看到这些事件,button2 应该是我们自定义 Button 类的一个实例。我们的小部件类的一部分如下所示

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    button2 = new Button(this);
    button2->setText("I am the phoenix king");
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::on_button1_clicked()
{
    QMouseEvent * mEvt = new QMouseEvent(QEvent::MouseButtonRelease, QPointF(10,10), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
    if(QApplication::sendEvent(button2, mEvt)){
        qDebug() << "Event accepted";
    }else{
        qDebug() <<"Event rejected";
    }
}

button1 是在 ui 表单中定义的,但上面可以看到其连接的插槽。在其中,我们正在制作一个鼠标事件,并为其提供所有相关信息,例如哪个鼠标按钮和单击的位置,然后我们使用静态 QApplication::sendEvent() 方法将其发送到目标。因为此方法返回一个布尔值,指示事件是被接受 (true) 还是被忽略 (false),我们利用该返回值来调试输出该信息。

button2 将接收事件并且其相关的事件处理程序将被触发。如果您运行应用程序并单击 button1,您将看到 button2 将通过其事件处理程序进行响应。

Mouse release at  QPoint(10,10)
Event accepted

您可以将事件更改为 QEvent::MouseButtonPress 或 QEvent::MouseMove 以触发其他事件处理程序。使用 postEvent() 对事件进行排队同样简单。

void Widget::on_button1_clicked()
{
    QMouseEvent * mEvt = new QMouseEvent(QEvent::MouseButtonRelease, QPointF(10,10), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
    QApplication::postEvent(button3,mEvt);
}

运行应用程序会产生与 sendEvent 相同的效果。

概括

使用事件系统是任何 Qt C++ GUI 开发人员的一项关键技能。本指南将引导您了解什么是事件、它们与信号和插槽的比较以及如何使用事件。我们继续看一些您可以在小部件类中处理的最有趣的事件以及事件传播的含义。在后面的部分中,我们探讨了 Qt 事件系统提供与事件一起工作的不同方式,尽最大可能为您提供具体示例以立即使用这些概念。本文最后向您展示了如何使用 sendEvent() 和 postEvent() 将您自己的事件发送到对象。如果您有任何问题、建议或只是想开始讨论 Qt 的事件系统,请不要犹豫,在下面的评论中联系我。我希望这对你们有所帮助,并感谢您的阅读。

分类: C++标签: