Qt Event(Qt事件)

1.事件定义

事件(event)是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标,敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件是在对用户操作做出响应的时候发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。

2.事件与信号槽

一般来说,使用 Qt 编程时,我们并不会把主要精力放在事件上,因为在 Qt 中,需要我们关心的事件总会发出一个信号。比如,我们关心的是 QPushButton 的鼠标点击,但我们不需要关心这个鼠标点击事件,而是关心它的 clicked()信号。

信号槽: signal 由具体对象发出,然后会马上交给由connect 函数连接的 slot 进行处理。
事件: Qt 使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件进行处理。但是,必要的时候,Qt 的事件也是可以不进入事件队列,而是直接处理的。并且,事件还可以使用“事件过滤器”进行过滤。

总的来说,如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。比如,如果我们要自定义一个 QPushButton,那么我们就需要重写它的鼠标点击事件和键盘处理事件,并且在恰当的时候发出 clicked()信号。

3.事件循环、事件处理函数

我们在 main 函数里面创建了一个 QApplication 对象,然后调用了它的 exec()函数。其实,这个函数就是开始 Qt 的事件循环。在执行 exec()函数之后,程序将进入事件循环来监听应用程序的事件。

当事件发生时,Qt 将创建一个事件对象。Qt 的所有事件都继承于 QEvent 类。在事件对象创建完毕后,Qt 将这个事件对象传递给 QObject 的 event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)

例如在所有组件的父类 QWidget 中,定义了很多事件处理函数,如 keyPressEvent()、keyReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent ()、mousePressEvent()、mouseReleaseEvent()等。这些函数都是 protected virtual 的,也就是说,我们应该在子类中重定义这些函数。

#include <QApplication> 
#include <QWidget> 
#include <QLabel> 
#include <QMouseEvent> 

class EventLabel : public QLabel 
{ 

protected: 
        void mouseMoveEvent(QMouseEvent *event); 
        void mousePressEvent(QMouseEvent *event); 
        void mouseReleaseEvent(QMouseEvent *event); 
}; 

void EventLabel::mouseMoveEvent(QMouseEvent *event) 
{ 
        this->setText(QString("<center><h1>Move: (%1, %2)</h1></center>").arg(QString::number(event->x()), QString::number(event->y()))); 
} 

void EventLabel::mousePressEvent(QMouseEvent *event) 
{ 
        this->setText(QString("<center><h1>Press: (%1, %2)</h1></center>").arg(QString::number(event->x()), QString::number(event->y()))); 
} 

void EventLabel::mouseReleaseEvent(QMouseEvent *event) 
{ 
        QString msg; 
        msg.sprintf("<center><h1>Release: (%d, %d)</h1></center>",event->x(), event->y()); 
        this->setText(msg); 
} 

int main(int argc, char *argv[]) 
{ 
        QApplication app(argc, argv); 
        EventLabel *label = new EventLabel; 
        label->setWindowTitle("MouseEvent Demo"); 
        label->resize(300, 200); 
        label->show(); 
        return app.exec(); 
}

这里我们继承了 QLabel 类,重写了 mousePressEvent、mouseMoveEvent 和 MouseReleaseEvent三个函数。我们并没有添加什么功能,只是在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)时把坐标显示在这个 Label 上面。

4.事件接受与忽略

前面的代码,我们在子类中重写了事件函数,以便让这些子类按照我们的需要完成某些功能,就像下面的代码:

void MyLabel::mousePressEvent(QMouseEvent * event)
{
        if(event->button() == Qt::LeftButton) {
                // do something
        } else {
                QLabel::mousePressEvent(event);
        }
}

上面的代码和前面类似,在鼠标按下的事件中检测,如果按下的是左键,做我们的处理工作,如果不是左键,则调用父类的函数。这在某种程度上说,是把事件向上传递给父类去响应,也就是说,我们在子类中“忽略”了这个事件。

我们可以把 Qt 的事件传递看成链状:如果子类没有处理这个事件,就会继续向其他类传递。其实,Qt的事件对象都有一个 accept()函数和 ignore()函数。正如它们的名字,前者用来告诉 Qt,事件处理函数“接收”了这个事件,不要再传递;后者则告诉 Qt,事件处理函数“忽略”了这个事件,需要继续传递,寻找另外的接受者。在事件处理函数中,可以使用 isAccepted()来查询这个事件是不是已经被接收了。

事实上,我们很少使用 accept()和 ignore()函数,而是像上面的示例一样,如果希望忽略事件,只要调用父类的响应函数即可。

Qt 中的事件大部分是 protected 的,因此,重写的函数必定存在着其父类中的响应函数,这个方法是可行的。为什么要这么做呢?因为我们无法确认父类中的这个处理函数没有操作,如果我们在子类中直接忽略事件,Qt 不会再去寻找其他的接受者,那么父类的操作也就不能进行,这可能会有潜在的危险。

在一个情形下,我们必须使用 accept()和 ignore()函数,那就是在窗口关闭的时候。如果你在窗口关闭时需要有个询问对话框,那么就需要这么去写:

void MainWindow::closeEvent(QCloseEvent * event)
{
        if(continueToClose()) {
                event->accept();
        } else {
                event->ignore();
        }
}

bool MainWindow::continueToClose()
{
        if(QMessageBox::question(this,
                                            tr("Quit"),
                                            tr("Are you sure to quit this application?"),
                                            QMessageBox::Yes | QMessageBox::No,
                                            QMessageBox::No)
                == QMessageBox::Yes) {
                return true;
        } else {
                return false;
        }
}

这样,我们经过询问之后才能正常退出程序。

5.event()函数

事件对象创建完毕后,Qt 将这个事件对象传递给 QObject的 event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。

event()函数主要用于事件的分发,所以,如果你希望在事件分发之前做一些操作,那么,就需要注意这个 event()函数了。为了达到这种目的,我们可以重写 event()函数。

例如,如果你希望在窗口中的tab 键按下时将焦点移动到下一组件,而不是让具有焦点的组件处理,那么你就可以继承 QWidget,并重写它的 event()函数,已达到这个目的:

bool MyWidget::event(QEvent *event) {
        if (event->type() == QEvent::KeyPress) {
                QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
                if (keyEvent->key() == Qt::Key_Tab) {
                        // 处理 Tab 鍵
                        return true;
                }
        }
        return QWidget::event(event);
}

event()函数接受一个 QEvent 对象,也就是需要这个函数进行转发的对象。为了进行转发,必定需要有一系列的类型判断,这就可以调用 QEvent 的 type()函数,其返回值是 QEvent::Type 类型的枚举。

我们处理过自己需要的事件后,可以直接 return 回去,对于其他我们不关心的事件,需要调用父类的 event()函数继续转发,否则这个组件就只能处理我们定义的事件了。

event()函数返回值是 bool 类型,如果传入的事件已被识别并且处理,返回 true,否则返回 false。如果返回值是 true,QApplication 会认为这个事件已经处理完毕,会继续处理事件队列中的下一事件;如果返回值是 false,QApplication 会尝试寻找这个事件的下一个处理函数。

event()函数的返回值和事件的 accept()和 ignore()函数不同。accept()和ignore()函数用于不同的事件处理器之间的沟通,例如判断这一事件是否处理;event()函数的返回值主要是通知QApplication 的 notify()函数是否处理下一事件。

为了更加明晰这一点,我们来看看 QWidget 的event()函数是如何定义的:

bool QWidget::event(QEvent *event) {
        switch (e->type()) {
        case QEvent::KeyPress:
                 keyPressEvent((QKeyEvent *)event);
                if (!((QKeyEvent *)event)->isAccepted())
                        return false;
                break;
        case QEvent::KeyRelease:
                keyReleaseEvent((QKeyEvent *)event);
                if (!((QKeyEvent *)event)->isAccepted())
                        return false;
                break;
                // more...
        }
        return true;
}

QWidget 的 event()函数使用一个巨大的 switch 来判断 QEvent 的 type,并且分发给不同的事件处理函数。在事件处理函数之后,使用这个事件的 isAccepted()方法,获知这个事件是不是被接受,如果没有被接受则 event()函数立即返回 false,否则返回 true。

另外一个必须重写 event()函数的情形是有自定义事件的时候。如果你的程序中有自定义事件,则必须重写 event()函数以便将自定义事件进行分发,否则你的自定义事件永远也不会被调用。

6.事件过滤器

Qt 创建了 QEvent 事件对象之后,会调用 QObject 的 event()函数做事件的分发。有时候,你可能需要在调用 event()函数之前做一些另外的操作,比如,对话框上某些组件可能并不需要响应回车按下的事件,此时,你就需要重新定义组件的 event()函数。如果组件很多,就需要重写很多次 event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来判断是否需要调用 event()函数。

QOjbect 有一个 eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event )
如果 watched 对象安装了事件过滤器,这个函数会被调用并进行事件过滤,然后才轮到组件进行事件处理。在重写这个函数时,如果你需要过滤掉某个事件,例如停止对这个事件的响应,需要返回 true

bool MainWindow::eventFilter(QObject *obj, QEvent *event)
 {
         if (obj == textEdit) {
                 if (event->type() == QEvent::KeyPress) {
                         QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
                         qDebug() << "Ate key press" << keyEvent->key();
                         return true;
                 } else {
                         return false;
                 }
         } else {
                 // pass the event on to the parent class
                 return QMainWindow::eventFilter(obj, event);
         }
 }

上面的例子中为 MainWindow 建立了一个事件过滤器。为了过滤某个组件上的事件,首先需要判断这个对象是哪个组件,然后判断这个事件的类型。

例如,我不想让 textEdit 组件处理键盘事件,于是就首先找到这个组件,如果这个事件是键盘事件,则直接返回 true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回 false。对于其他组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。

在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用 installEventFilter()函数。这个函数的签名如下:
void QObject::installEventFilter ( QObject * filterObj )
这个函数是 QObject 的一个函数,因此可以安装到任何 QObject 的子类,并不仅仅是 UI 组件。这个函数接收一个 QObject 对象,调用了这个函数安装事件过滤器的组件会调用 filterObj 定义的eventFilter()函数。

例如,textField.installEventFilter(obj),则如果有事件发送到textField 组件是,会先调用 obj->eventFilter()函数,然后才会调用 textField.event()。

当然,你也可以把事件过滤器安装到 QApplication 上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。

如果一个组件安装了多个过滤器,则最后一个安装的会最先调用,类似于堆栈的行为

注意: 如果你在事件过滤器中 delete 了某个接收组件,务必将返回值设为 true。否则,Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。

事件过滤器和被安装的组件必须在同一线程,否则,过滤器不起作用。另外,如果在 install 之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。

事件的调用最终都会调用 QCoreApplication 的 notify()函数,因此,最大的控制权实际上是重写QCoreApplication 的 notify()函数。由此可以看出,Qt 的事件处理实际上是分层五个层次:

  1. 重定义事件处理函数
  2. 重定义 event()函数
  3. 为单个组件安装事件过滤器
  4. 为 QApplication 安装事件过滤器
  5. 重定义 QCoreApplication 的 notify()函数

这几个层次的控制权是逐层增大的。

7.自定义事件

Qt 允许创建自己的事件类型,这在多线程的程序中尤其有用,当然,也可以用在单线程的程序中,作为一种对象间通讯的机制。那么,为什么需要使用事件,而不是使用信号槽呢?主要原因是,事件的分发既可以是同步的,又可以是异步的,而函数的调用或者说是槽的回调总是同步的。事件的另外一个好处是,它可以使用过滤器。

Qt 中的自定义事件很简单,同其他类似的库的使用很相似,都是要继承一个类进行扩展。在 Qt 中,你需要继承的类是 QEvent

继承 QEvent 类,你需要提供一个QEvent::Type 类型的参数,作为自定义事件的类型值。这里的QEvent::Type 类型是 QEvent 里面定义的一个 enum,因此,你是可以传递一个 int 的。重要的是,你的事件类型不能和已经存在的 type 值重复,否则会有不可预料的错误发生!因为系统会将你的事件当做系统事件进行派发和调用。

在 Qt 中,系统将保留0 - 999的值,也就是说,你的事件 type 要大于999. 具体来说,你的自定义事件的 type 要在 QEvent::User 和 QEvent::MaxUser 的范围之间。其中,QEvent::User 值是1000,QEvent::MaxUser 的值是65535。从这里知道,你最多可以定义64536个事件,相信这个数字已经足够大了!

但是,即便如此,也只能保证用户自定义事件不能覆盖系统事件,并不能保证自定义事件之间不会被覆盖。为了解决这个问题,Qt 提供了一个函数:registerEventType(),用于自定义事件的注册。该函数签名如下:
static int QEvent::registerEventType ( int hint = -1 );
函数是 static 的,因此可以使用 QEvent 类直接调用。函数接受一个 int 值,其默认值为-1,返回值是创建的这个 Type 类型的值。如果 hint 是合法的,不会发生任何覆盖,则会返回这个值;如果hint 不合法,系统会自动分配一个合法值并返回。因此,使用这个函数即可完成 type 值的指定。这个函数是线程安全的,因此不必另外添加同步。

你可以在 QEvent 子类中添加自己的事件所需要的数据,然后进行事件的发送。Qt 中提供了两种发送方式:
static bool QCoreApplication::sendEvent(QObjecy receiver, QEvent event):事件被 QCoreApplication 的 notify()函数直接发送给 receiver 对象,返回值是事件处理函数的返回值。使用这个函数必须要在栈上创建对象,例如:

QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
QApplication::sendEvent(mainWindow, &event);

static bool QCoreApplication::postEvent(QObject receiver, QEvent event):事件被 QCoreApplication 追加到事件列表的最后,并等待处理,该函数将事件追加后会立即返回,并且注意,该函数是线程安全的。另外一点是,使用这个函数必须要在堆上创建对象,例如:

QApplication::postEvent(object, new MyEvent(QEvent::registerEventType(2048)));

这个对象不需要手动 delete,Qt 会自动 delete 掉!因此,如果在 post 事件之后调用 delete,程序可能会崩溃。另外,postEvent()函数还有一个重载的版本,增加一个优先级参数,具体请参见API。通过调用 sendPostedEvent()函数可以让已提交的事件立即得到处理。
如果要处理自定义事件,可以重写 QObject 的 customEvent()函数,该函数接收一个 QEvent 对象作为参数。可以像前面介绍的重写 event()函数的方法去重写这个函数:

void CustomWidget::customEvent(QEvent *event) {
        CustomEvent *customEvent = static_cast<CustomEvent *>(event);
        // ....
}

另外,你也可以通过重写 event()函数来处理自定义事件:

bool CustomWidget::event(QEvent *event) {
        if (event->type() == MyCustomEventType) {
                CustomEvent *myEvent = static_cast<CustomEvent *>(event);
                // processing...
                return true;
        }
        return QWidget::event(event);
}

这两种办法都是可行的。

  • 16
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值