Qt事件类 QEvent

QEvent是Qt中所有事件的基类,事件对象包含了该次事件所携带的相关参数。其成员函数如下:

QEvent(Type type)
virtual ~QEvent()
void accept()
void ignore()
bool isAccepted() const
void setAccepted(bool accepted)
bool spontaneous() const
Type type() const
其中,我们在开发中经常用到的就是accept() 和 ignore()函数。当然,accept() 等价于setAccepted(true),ignore() 等价于setAccepted(false)。

accept()设置accept标志,表明事件接收者想要处理这个事件,不要传递给其父窗口。

ignore() 于此相反,清除accept标志,即表明事件接收者不想处理该事件,请将事件继续往父窗口传递。


在一前将QCoreApplication类时,我们也提到过,Qt的主事件循环从本地窗口系统中获取事件,然后把他们转换成QEvent对象,再把它们发送给具体的事件接收者。通常情况下,事件都来自于底层窗口系统,但我们也可以手动的发送一个事件通过QCoreApplication::sendEvent() 和 QCoreApplication::postEvent() 函数。另外,可以使用QEvent的成员函数spontaneous() 来判断一个事件是不是手动发出的。

QObject通过调用event() 函数来响应事件。该函数可以被子类重写来自定义事件处理方式,也可以借此处理自定义的事件类型。默认情况下,Qt的事件系统会直接把事件分发给事件接受者的具体的事件处理器,比如QObject::timerEvent()、QWidget::mouseMoveEvent()等。但可以使用QObject::installEventFilter() 函数通过事件过滤器来让一个对象拦截另一个对象的事件。

QEvent类本身只包含一个事件类型的成员和一个"accept"标志。accept标志可以通过accept() 函数进行设置,使用ignore() 函数进行清除设置。默认情况下是被设置的,即accept为true。但我们不能依赖这个默认值,因为子类随时都有可能清除这个值。而Type在QEvent中是一个枚举类型,列出了当前Qt事件系统所支持的所有事件类型。除此之外,我们还可以自定义自己的事件类型,但要确保自定义的事件类型在Qt所要求的范围内,即QEvent::User~QEvent::MaxUser,且在整个应用程序中是唯一的,为了确保这一点,可以在自定义事件类型时,使用QEvent的静态成员函数registerEventType() 让Qt系统为我们选择一个当前合法且唯一的事件类型值。

下面,我们就来实现一个简单的自定义事件:

打开QtCreator,新建一个GUI程序,选择QWidget作为我们窗口的基类。

我们先在widget.h中定义我们的事件类型。代码如下:

const QEvent::Type MyType = (QEvent::Type)QEvent::registerEventType();
class MyEvent : public QEvent
{
public:
    MyEvent(QEvent::Type type, QString param) : QEvent(type)
    {
        this->param = param;
    }
    QString param;
};
在此,我们先使用registerEventType() 函数注册了我们的事件类型MyType;然后,从QEvent派生了我们自己的事件类,并再QEvent的基础之上添加了一个成员,用来传递一些简单的字符串信息。

接下来,我们在窗口上拖入一个按钮,我们实现当点击按钮时,向当前窗口发送一个我们自定义的事件。在按钮上点击右键->转到槽,实现其槽函数如下:

void Widget::on_pushButton_clicked()
{
    MyEvent e(MyType, "自定义事件");
    QApplication::sendEvent(this, &e);
}
在此,我们使用sendEvent()还发送事件。在之前的文章中,我们就讲过,sendEvent()只有在事件被处理完成后才返回,并且不会自动帮我们释放事件对象,所以推荐在栈上创建事件对象。并且,我们把sendEvent() 的第一个参数,即事件的接受者指定为了this,即让当前窗口响应该事件。所以,我们还应该重写窗口类的event() 函数,来处理我们的自定义事件。代码如下:

bool Widget::event(QEvent *event)
{
    if(event->type() == MyType)
    {
        MyEvent *e = static_cast(event);
        QMessageBox::information(this, "MyEvent", e->param);
        return true;
    }
    return QWidget::event(event);
}
根 据事件的类型,具体处理我们要处理的自定义事件,在此我们只是简单的弹出一个消息框,显示事件对象携带的信息,然后返回true。实际开发中,可以根据自己的需要,进行其他的逻辑处理。返回true表示我们已处理过该事件。而显示调用了accept() 表示,不需要进一步将该事件想父窗口传递。

其测试结果如下:

Qt事件类 QEvent_第1张图片

经过上面对QEvent类的学习和自定义事件的使用,我们一对Qt的事件机制有了大概的了解。那么,下面我们就在详细的看一下Qt的事件系统,了解一下Qt中的事件的产生、传递和处理过程。在Qt中,事件是有QEvent类或其子类对象来表示。一个事件对象表示了发生在应用程序里的某件事,或者是应用程序需要知道的某个外部活动的结果。事件可以被任何QObject子类的实例进行接收和处理,大部分和GUI控件相关,小部分是非GUI相关,比如文件读写,socket等。

事件的传递

当一个事件产生后,Qt就会创建一个某种QEvent子类的对象去表示它,然后使用event() 函数将它发送给某个具体的QObject或其子类的实例。但这个函数本身并不处理事件。它只是根据具体的事件类型,调用一个事件处理器,然后返回一个事件处理的结果,即accept或ignore。不同的事件有不同的来源,比如,QMouseEvent和QKeyEvent来自于窗口系统,QTimerEvent来自于定时器;还有一些来自于应用程序本身。

事件的类型

绝大部分事件类型都由特定的类来表示,比如常见的QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent、QCloseEvent等。每一个具体的事件类都是从QEvent派生下来的,并添加了与该事件相关的函数。例如,QResizeEvent添加了size()和oldSize() 函数,以使控件知道它的尺寸发生了怎样的变化。并且,有些事件类不止表示一种事件,比如QMouseEvent,可以表示鼠标按下,鼠标双击,鼠标移动,以及其他相关操作。

每一种事件都有一个相关的类型,由QEvent::Type定义。这可以帮助我们在程序运行时确定当前的事件对象具体是由哪一个事件类构造出来的。

事件处理器

递送一个事件的通常方式是调用一个虚函数。例如,QPaintEvent通过调用QWidget::paintEvent() 来递送,这个虚函数有责任作出正确的反应,通常是重绘控件。如果你实现了这个虚函数,但没进行所以必要的功能,那么你可能需要调用一下基类的实现。例如,下面的代码处理了一个check box上的鼠标左键事件,同时把所有其他的鼠标事件传递到了基类进行处理:

  void MyCheckBox::mousePressEvent(QMouseEvent *event)
  {
      if (event->button() == Qt::LeftButton) {
          // handle left mouse button here
      } else {
          // pass on other buttons to base class
          QCheckBox::mousePressEvent(event);
      }
  }
如果你想取代基类的函数,那么你必须自己实现所有的功能。但是,如果你只是想扩展基类的函数功能,那么你可以先实现你想要实现的功能,然后调用基类的函数对你所没有处理的事情进行默认处理。

偶尔,也会遇到没有事件特定的响应函数,或者事件特定的函数是不够的。最常见的例子就是Tab 键按下事件。正常情况下,QWidget对这个事件的处理是移动键盘焦点到下一个控件,但是还有一些控件自身需要tab键。这时,这些需要tab键的控件就可以重新实现QObject::event() 函数,这是一个通用的事件处理器,它们可以在这个函数的常规处理之前或之后进行它们自己需要的工作,甚至可以完全取代该函数。例如,如果一个自定义控件既要处理Tab键有要处理程序特定的自定义事件,则可以如下实现event() 函数:

  bool MyWidget::event(QEvent *event)
  {
      if (event->type() == QEvent::KeyPress) {
      QKeyEvent *ke = static_cast(event);
      if (ke->key() == Qt::Key_Tab) {
          // special tab handling here
          return true;
      }
      } else if (event->type() == MyCustomEventType) {
      MyCustomEvent *myEvent = static_cast(event);
      // custom event handling here
      return true;
      }

      return QWidget::event(event);
  }

记住,仍然需要为所有你为处理的情况调用event() 函数,并且该函数中的返回值表明了事件是否被处理了:返回true可以防止事件再被发送给其他的对象。

事件过滤器

有时,一个对象可能需要先窥探或拦截发送给另一个对象的事件。例如,对话框通常想为一些控件过滤按键事件。可以使用QObject::installEventFilter() 函数设置第一个事件过滤器来完成该功功能,让被安装的过滤器对象在自己的eventFilter() 函数中接收发往目标对象的事件。事件过滤器可以在事件发给目标对象之前先处理事件,可以根据需要检查或丢弃事件。当然,相对的也可以使用QObject::removeEventFilter() 函数移除一个现存的过滤器。当过滤器的eventFilter() 函数被调用时,它可以接受或拒绝该事件,也可以允许或阻止应用程序继续处理该事件。如果所有的事件过滤器都允许应用程序继续处理该事件,那么该事件才被发给目标对象。如果有一个事件过滤器阻止了应用程序继续处理该事件(通过在eventFilter() 函数中返回true),那么这个事件就不会被发送给后续的事件过滤器及目标对象。

  bool FilterObject::eventFilter(QObject *object, QEvent *event)
  {
      if (object == target && event->type() == QEvent::KeyPress) {
          QKeyEvent *keyEvent = static_cast(event);
          if (keyEvent->key() == Qt::Key_Tab) {
              // Special tab handling
              return true;
          } else
              return false;
      }
      return false;
  }
上面的代码展示了拦截tab键的另一种方式。在这种实现方式下,事件过滤器处理相关的事件并且返回true阻止事件继续传播。而所有其他的事件被过滤器忽略,过滤器通过返回false允许这些事件被发送给目标对象或者其他的事件过滤器。

我们也可以在QApplication 或者 QCoreApplication对象上安装过滤器来过滤整个应用程序的所有事件。这种全局的事件过滤器会在特定对象的事件过滤器之前被调用。这种过滤器功能强大,但是它也减慢了整个程序的事件传送。所以,我们在开发中,应该首先使用其他的技术。

发送事件

有的应用程序想要创建并发送它们自己的事件。你可以通过创建合适的事件对象,然后用和Qt自己的事件循环一模一样的方式调用QCoreApplication::sendEvent() 和 QCoreApplication::postEvent() 来完成。sendEvent() 会立即处理事件。当它返回时,事件过滤器或目标对象就已经处理了事件。对于很多事件类来说,都有一个isAccepted()函数来告诉你事件是被最后一个调用的事件处理器接收了还是拒绝了(rejected)。

postEvent() 会将事件投递到一个队列中以被后续分发。在下一次Qt的主事件循环运行时,就会分发所有已被投递的事件,当然,Qt会进行一些优化。例如,如果队列中存在几个resize事件,Qt就会将他们合并成一个进行分发。对应窗口的重绘事件也是如此:QWidget::update() 会调用postEvent(),Qt也会对多个重绘事件进行合并,已避免由于多次回调导致的界面闪烁。

postEvent()也会在对象的初始化过程中使用,因为被投递的事件通常在对象初始化完成之后很快就会被分发。在实现一个控件时,我们必须意识到控件会在生命周期开始后,很快就会收到事件循环分发来的事件,所以,在构造函数中,我们要确保在控件接收到任何事件之前完成成员变量的初始化。

以上,就是对Qt事件系统的方方面面进行的简单阐述。至于熟练使用,还需要大家在实际代码中进行操练。

你可能感兴趣的:(Qt)