事件

事件(event)是由系统或者 Qt 本身在不同的时刻发出的。

如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。比如,如果我们要自定义一个能够响应鼠标事件的EventLabel,我们就需要重写QLabel的鼠标事件,做出我们希望的操作,有可能还得在恰当的时候发出一个类似按钮的clicked()信号(如果我们希望让这个EventLabel能够被其它组件使用)或者其它的信号。

#include <QApplication>
#include <QLabel>
#include <QString>
#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 a(argc, argv);

    EventLabel *label = new EventLabel;
    label->setWindowTitle("MouseEvent Demo");
    label->resize(300, 200);
    label->show();

    return a.exec();
}

EventLabel继承了QLabel,覆盖了mousePressEvent()、mouseMoveEvent()和MouseReleaseEvent()三个函数。我们并没有添加什么功能,只是在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)的时候,把当前鼠标的坐标值显示在这个Label上面。由于QLabel是支持
HTML 代码的,因此我们直接使用了 HTML 代码来格式化文字。

补充:
QString的arg()函数可以自动替换掉QString中出现的占位符。其占位符以 % 开始,后面是占位符的位置,例如 %1,%2 这种。

QString("[%1, %2]").arg(x, y);

类似 C 风格的格式化函数sprintf()来构造QString

为什么要点击鼠标之后才能在mouseMoveEvent()函数中显示鼠标坐标值?这是因为QWidget中有一个mouseTracking属性,该属性用于设置是否追踪鼠标。只有鼠标被追踪时,mouseMoveEvent()才会发出。如果mouseTracking是 false(默认即是),组件在至少一次鼠标点击之后,才能够被追踪,也就是能够发出mouseMoveEvent()事件。如果mouseTracking为 true,则mouseMoveEvent()直接可以被发出。知道了这一点,我们就可以在main()函数中直接设置下:

EventLabel *label = new EventLabel;
label->setWindowTitle("MouseEvent Demo");
label->resize(300, 200);
label->setMouseTracking(true);
label->show();

Qt 学习之路 2(18):事件

事件的接受与忽略

accept()和ignore()。

Qt 的事件对象有两个函数:accept()和ignore()。
正如它们的名字一样,前者用来告诉 Qt,这个类的事件处理函数想要处理这个事件;
后者则告诉 Qt,这个类的事件处理函数不想要处理这个事件。
在事件处理函数中,可以使用isAccepted()来查询这个事件是不是已经被接收了。具体来说:如果一个事件处理函数调用了一个事件对象的accept()函数,这个事件就不会被继续传播给其父组件;如果它调用了事件的ignore()函数,Qt 会从其父组件中寻找另外的接受者。

QEvent默认是accept(),QWidget默认是ignore()

Qt 做了特殊的设计:事件对象默认是 accept
的,而作为所有组件的父类QWidget的默认实现则是调用ignore()。这么一来,如果你自己实现事件处理函数,不调用QWidget的默认实现,你就等于是接受了事件;如果你要忽略事件,只需调用QWidget的默认实现。这一点我们前面已经说明。

class CustomButton : public QPushButton
{
    Q_OBJECT
public:
    CustomButton(QWidget *parent) : QPushButton(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "CustomButton";
    }
};

class CustomButtonEx : public CustomButton
{
    Q_OBJECT
public:
    CustomButtonEx(QWidget *parent) : CustomButton(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "CustomButtonEx";
    }
};

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    CustomWidget(QWidget *parent) : QWidget(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
         qDebug() << "CustomWidget";
        //QWidget::mousePressEvent(event);
        event->ignore();
    }
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = 0) : QMainWindow(parent)
    {
        CustomWidget *widget = new CustomWidget(this);
        CustomButton *cbex = new CustomButton(widget);
        cbex->setText(tr("CustomButton"));
        CustomButtonEx *cb = new CustomButtonEx(widget);
        cb->setText(tr("CustomButtonEx"));
        QVBoxLayout *widgetLayout = new QVBoxLayout(widget);
        widgetLayout->addWidget(cbex);
        widgetLayout->addWidget(cb);
        this->setCentralWidget(widget);
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "MainWindow";
    }
};

MainWindow中添加了一个CustomWidget,里面有两个按钮对象:CustomButton和CustomButtonEx。每一个类都重写了mousePressEvent()函数。

运行使用CustomWidget
会发现:
点击CustomButtonEx,输出的是CustomButtonEx
点击CustomButton,输出的是CustomButton
点击QWidget,输出的是QWidget;
如果添加
QWidget::mousePressEvent(event);
或者event->ignore();
会输出MainWindow;

这说明了
QEvent的默认调用是event->accept();
QWidget的默认是调用event->ignore();

特殊情况使用

在一个特殊的情形下,我们必须使用accept()和ignore()函数,那就是窗口关闭的事件。对于窗口关闭QCloseEvent事件,调用accept()意味着
Qt 会停止事件的传播,窗口关闭;调用ignore()则意味着事件继续传播,即阻止窗口关闭。

Qt 学习之路 2(19):事件的接受与忽略

event()

事件对象创建完毕后,Qt将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。类似安卓事件分发器
如上所述,event()函数主要用于事件的分发。所以,如果你希望在事件分发之前做一些操作,就可以重写这个event()函数了。例如,我们希望在一个QWidget组件中监听tab 键的按下,那么就可以继承QWidget,并重写它的event()函数,来达到这个目的

bool CustomWidget::event(QEvent *e)
{
    if (e->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
        if (keyEvent->key() == Qt::Key_Tab) {
            qDebug() << "You press tab.";
            return true;
        }
    }
    return QWidget::event(e);
}

CustomWidget是一个普通的QWidget子类。我们重写了它的event()函数,这个函数有一个QEvent对象作为参数,也就是需要转发的事件对象。函数返回值是
bool 类型。如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是
true,并且,该事件对象设置了accept(),那么 Qt
会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件。

注意,在event()函数中,调用事件对象的accept()和ignore()函数是没有作用的,不会影响到事件的传播。

我们可以通过使用QEvent::type()函数可以检查事件的实际类型,其返回值是QEvent::Type类型的枚举。
我们处理过自己感兴趣的事件之后,可以直接返回true,表示我们已经对此事件进行了处理;
返回false表示事件根本不会被转发。
对于其它我们不关心的事件,则需要调用父类的event()函数继续转发,否则这个组件就只能处理我们定义的事件了。

event()是一个集中处理不同类型的事件的地方。如果你不想重写一大堆事件处理器,就可以重写这个event()函数,通过QEvent::type()判断不同的事件。鉴于重写event()函数需要十分小心注意父类的同名函数的调用,一不留神就可能出现问题,所以一般还是建议只重写事件处理器(当然,也必须记得是不是应该调用父类的同名处理器)。这其实暗示了event()函数的另外一个作用:屏蔽掉某些不需要的事件处理器。正如我们前面的CustomTextEdit例子看到的那样,我们创建了一个只能响应
tab 键的组件。这种作用是重写事件处理器所不能实现的。

Qt 学习之路 2(20):event()

事件过滤器

event()和事件过滤器的区别

通过前面的章节,我们已经知道,Qt 创建了QEvent事件对象之后,会调用QObject的event()函数处理事件的分发。
显然,我们可以在event()函数中实现拦截的操作。由于event()函数是 protected 的,因此,需要继承已有类。 如果组件很多,就需要重写很多个event()函数。这当然相当麻烦,更不用说重写event()函数还得小心一堆问题。

好在 Qt 提供了另外一种机制来达到这一目的:事件过滤器。

QObject有一个eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:

virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );

这个函数正如其名字显示的那样,是一个“事件过滤器”。所谓事件过滤器,可以理解成一种过滤代码。想想做化学实验时用到的过滤器,可以将杂质留到滤纸上,让过滤后的液体溜走。
事件过滤器也是如此:它会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。
这个函数返回一个bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回false。事
件过滤器的调用时间是目标对象(也就是参数里面的watched对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched对象以及以后所有的事件过滤器根本不会知道这么一个事件。

实现一个事件过滤包括两个步骤:

  1. 在目标对象上调用installEventFilter(),注册监视对象。
  2. 在监视对象的eventFilter()函数中处理目标对象的事件。
class MainWindow : public QMainWindow
 {
 public:
     MainWindow();
 protected:
     bool eventFilter(QObject *obj, QEvent *event);
 private:
     QTextEdit *textEdit;
 };

 MainWindow::MainWindow()
 {
     textEdit = new QTextEdit;
     setCentralWidget(textEdit);
	
	//1.在目标对象上调用installEventFilter(),注册监视对象。
     textEdit->installEventFilter(this);
 }

//在监视对象的eventFilter()函数中处理目标对象的事件。
 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是我们定义的一个类。我们重写了它的eventFilter()函数。
为了过滤特定组件上的事件,首先需要判断这个对象是不是我们感兴趣的组件,然后判断这个事件的类型。
在上面的代码中,我们不想让textEdit组件处理键盘按下的事件。所以,首先我们找到这个组件,如果这个事件是键盘事件,则直接返回true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回
false。对于其它的组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数

eventFilter()函数相当于创建了过滤器,然后我们需要安装这个过滤器。安装过滤器需要调用QObject::installEventFilter()函数。这个函数的签名如下:

void QObject::installEventFilter ( QObject * filterObj )

事件过滤器的强大之处在于,我们可以为整个应用程序添加一个事件过滤器。记得,installEventFilter()函数是QObject的函数,QApplication或者QCoreApplication对象都是QObject的子类,因此,我们可以向QApplication或者QCoreApplication添加事件过滤器。这种全局的事件过滤器将会在所有其它特性对象的事件过滤器之前调用。尽管很强大,但这种行为会严重降低整个应用程序的事件分发效率。因此,除非是不得不使用的情况,否则的话我们不应该这么做。

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

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

事件总结

事件处理办法

win32 API 的WndProc()函数

第一,所有事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支语句进行选择,其代表作就是 win32 API 的WndProc()函数:

LRESULT CALLBACK WndProc(HWND hWnd,
                          UINT message,
                          WPARAM wParam,
                          LPARAM lParam)

在这个函数中,我们需要使用switch语句,选择message参数的类型进行处理,典型代码是:

switch(message) {
     case WM_PAINT:
         // ...
         break;
     case WM_DESTROY:
         // ...
         break;
     ...
}

Qt机制 每一种事件对应一个事件处理函数。

mouseEvent()
keyPressEvent()
...

Qt 具有这么多种事件处理函数,肯定有一个地方对其进行分发,否则,Qt 怎么知道哪一种事件调用哪一个事件处理函数呢?这个分发的函数,就是event()。显然,当QMouseEvent产生之后,event()函数将其分发给mouseEvent()事件处理器进行处理。

event()函数会有两个问题:

  1. QWidget::event()函数是一个 protected 的函数,这意味着我们要想重写event(),必须继承一个已有的类。试想,我的程序根本不想要鼠标事件,程序中所有组件都不允许处理鼠标事件,是不是我得继承所有组件,一一重写其event()函数?protected
    函数带来的另外一个问题是,如果我基于第三方库进行开发,而对方没有提供源代码,只有一个链接库,其它都是封装好的。我怎么去继承这种库中的组件呢?

  2. event()函数的确有一定的控制,不过有时候我的需求更严格一些:我希望那些组件根本看不到这种事件。event()函数虽然可以拦截,但其实也是接收到了QMouseEvent对象。我连让它收都收不到。这样做的好处是,模拟一种系统根本没有那个事件的效果,所以其它组件根本不会收到这个事件,也就无需修改自己的事件处理函数。这种需求怎么办呢?

这两个问题是event()函数无法处理的。
于是,Qt提供了另外一种解决方案:**事件过滤器。**事件过滤器给我们一种能力,让我们能够完全移除某种事件。事件过滤器可以安装到任意QObject类型上面,并且可以安装多个。如果要实现全局的事件过滤器,则可以安装到QApplication或者QCoreApplication上面。这里需要注意的是,如果使用installEventFilter()函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,只有这个对象的事件需要先传递给事件过滤器的eventFilter()函数进行过滤,其它对象不受影响。如果给QApplication对象安装事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给eventFilter()函数。

QCoreApplication::notify()

事实上,还有一种方法,我们没有介绍。Qt 事件的调用最终都会追溯到QCoreApplication::notify()函数,因此,最大的控制权实际上是重写QCoreApplication::notify()。这个函数的声明是:

virtual bool QCoreApplication::notify ( QObject * receiver, QEvent * event );

该函数会将event发送给receiver,也就是调用receiver->event(event),其返回值就是来自receiver的事件处理器。注意,这个函数为任意线程的任意对象的任意事件调用,因此,它不存在事件过滤器的线程的问题。不过我们并不推荐这么做,因为notify()函数只有一个,而事件过滤器要灵活得多。

事件处理方式的调用顺序

现在我们可以总结一下 Qt 的事件处理,实际上是有五个层次:

  1. 重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
  2. 重写event()函数。event()函数是所有对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
  3. 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
  4. 在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和notify()函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到
    disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
  5. 重写QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication是单例的)。

过滤强度递增

class Label : public QWidget
{
public:
    Label()
    {
        installEventFilter(this);
    }

    bool eventFilter(QObject *watched, QEvent *event)
    {
        if (watched == this) {
            if (event->type() == QEvent::MouseButtonPress) {
                qDebug() << "eventFilter";
            }
        }
        return false;
    }

protected:
    void mousePressEvent(QMouseEvent *)
    {
        qDebug() << "mousePressEvent";
    }

    bool event(QEvent *e)
    {
        if (e->type() == QEvent::MouseButtonPress) {
            qDebug() << "event";
        }
        return QWidget::event(e);
    }
};

class EventFilter : public QObject
{
public:
    EventFilter(QObject *watched, QObject *parent = 0) :
        QObject(parent),
        m_watched(watched)
    {
    }

    bool eventFilter(QObject *watched, QEvent *event)
    {
        if (watched == m_watched) {
            if (event->type() == QEvent::MouseButtonPress) {
                qDebug() << "QApplication::eventFilter";
            }
        }
        return false;
    }

private:
    QObject *m_watched;
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Label label;
    app.installEventFilter(new EventFilter(&label, &label));
    label.show();
    return app.exec();
}

我们可以看到,鼠标点击之后的输出结果是:

QApplication::eventFilter 
eventFilter 
event 
mousePressEvent

因此可以知道,全局事件过滤器被第一个调用,之后是该对象上面的事件过滤器,其次是event()函数,最后是特定的事件处理函数。

自定义事件

自定义事件条件

1.自定义值需要继承QEvent

2.QEvent::type 定义值

(Qt 中,系统保留 0 - 999 的值,边界值,QEvent::User的值是 1000,QEvent::MaxUser的值是 65535。)

3.避免和系统事件冲突,

Qt 提供了一个函数:registerEventType(),用于自定义事件的注册。

static int QEvent::registerEventType ( int hint = -1 ); //传入TYPE值

type值合法,则返回合法值;反之系统会自动分配一个合法值并返回。

4.事件的发送

QEvent子类中添加自己的事件所需要的数据,然后进行事件的发送

sendEvent

static bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event);

直接将event事件发送给receiver接受者,使用的是QCoreApplication::notify()函数。函数返回值就是事件处理函数的返回值。

在事件被发送的时候,event对象并不会被销毁。通常我们会在栈上创建event对象,例如:

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

postEvent

static void QCoreApplication::postEvent(QObject *receiver, QEvent *event);

将event事件及其接受者receiver一同追加到事件队列中,函数立即返回。

因为 post 事件队列会持有事件对象,并且在其 post 的时候将其 delete掉,
因此,我们必须在堆上创建event对象。当对象被发送之后,再试图访问event对象就会出现问题(因为 post
之后,event对象就会被 delete)。
当控制权返回到主线程循环时,保存在事件队列中的所有事件都通过notify()函数发送出去。
事件会根据 post 的顺序进行处理。如果你想要改变事件的处理顺序,可以考虑为其指定一个优先级。默认的优先级是Qt::NormalEventPriority
这个函数是线程安全的。

Qt 还提供了一个函数:

static void QCoreApplication::sendPostedEvents(QObject *receiver, int event_type);

这个函数的作用是,将事件队列中的接受者为receiver,事件类似为 event_type 的所有事件立即发送给 receiver 进行处理。需要注意的是,来自窗口系统的事件并不由这个函数进行处理,而是processEvent().

5.处理自定义事件

1) customEvent()处理

我们可以重写QObject::customEvent()函数
我们可以通过转换 event 对象类型来判断不同的事件:

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

2) event()处理

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