zoukankan      html  css  js  c++  java
  • 事件接收与忽略

    事件接收与忽略

    来源 https://blog.51cto.com/devbean/225519

    本章内容也是关于Qt事件。或许这一章不能有一个完整的例子,因为对于事件总是感觉很抽象,还是从底层上理解一下比较好的吧!

    前面说到了事件的作用,下面来看看我们如何来接收事件。回忆一下前面的代码,我们在子类中重写了事件函数,以便让这些子类按照我们的需要完成某些功能,就像下面的代码:

    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不会再去寻找其他的接受者,那么父类的操作也就不能进行,这可能会有潜在的危险。另外我们查看一下QWidget的mousePressEvent()函数的实现:

    void QWidget::mousePressEvent(QMouseEvent *event)
    {
            event->ignore();
            if ((windowType() == Qt::Popup)) {
                    event->accept();
                    QWidget* w;
                    while ((w = qApp->activePopupWidget()) && w != this){
                            w->close();
                            if (qApp->activePopupWidget() == w) // widget does not want to dissappear
                                    w->hide(); // hide at least
                    }
                    if (!rect().contains(event->pos())){
                            close();
                    }
            }
    }


    请注意第一条语句,如果所有子类都没有覆盖mousePressEvent函数,这个事件会在这里被忽略掉,这暗示着这个组件不关心这个事件,这个事件就可能被传递给其父组件。

    不过,事情也不是绝对的。在一个情形下,我们必须使用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;
            }
    }


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

    ----------------------------

    Qt 中的事件系统

    来源 https://zhuanlan.zhihu.com/p/50053079   编辑于 2018-11-16

    本文结构如下:

    概述

    首先要明白的是:“在 Qt 里,一个事件就是一个对象,所有事件的祖先都来自于 QEvent”。意思就是说,只要有一个事件发生(如鼠标单击事件),此时就会有一个 QEvent 对象被创建出来,然后开始各种传送。由于 Qt 事件系统是依托于元对象系统的,所以所有的 QObject 类都可以接收/处理 QEvent 事件。

    说起事件,其实无非就是围绕着“产生-发送-处理”这个基本流程来说的。

    如何产生一个事件?

    这是最简单的知识点了,当然是创建一个对象啦。这问题和“如何创建一个整数”一样简单。我创建一个整数是“int i”,那我创建一个事件就是“MyEvent event(1, 2, 3)”啦。其中 MyEvent 是我自定义的一个事件类,123是我往这个事件传入的参数。当你不知道怎么创建一个事件对象时,更多的是不知道一个事件对象长啥样。所以我们先看下事件是什么样的一个类。

    事件类长什么样?

    这是我自定义的一个事件类:

    class MyEvent : public QEvent
    {
    public:
        MyEvent();
        MyEvent(int x, int y, int z);
    
        static const Type type;
    
        int x;
        int y;
        int z;
    };
    

    别忘了本文最开始的那句话“一个事件就是一个类对象”。你看我写的这个事件类,它里面可以存储三个 int 整数。假如我程序中触发了某某槽函数,产生了一个三维坐标事件,要把坐标发送出去,那我上面写的这个事件类就派上用场了。我创建一个事件对象,并把坐标参数传到这个事件对象里(MyEvent event(1, 2, 3)),这样这个 event 对象就产生了,它承载着一个三维坐标。

    Qt 自带的事件类功能更多

    Qt 自带的事件类有很多很多,有些事件类还附带了简单的函数,例如 QResizeEvent 这个事件类就有 size() 和 oldSize() 函数;有些事件类还支持多种实际操作,比如 QMouseEvent 支持鼠标单击、双击、移动等,也就是说当鼠标进行这些操作的时候,都会产生一个 QMouseEvent 事件对象。我上面写的那个例子是很简单的,只能存储 xyz 三个整数,我也可以定义多点变量或者函数。

    每一个事件类都有个唯一身份值:type值

    这个 type 值的作用就是我们在处理事件时用于识别事件类的代号。请看下面实际中的例子:

    bool FilterObject::eventFilter(QObject *object, QEvent *event)
    {
        if (object == target && event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
            if (keyEvent->key() == Qt::Key_Tab) {
                ......
                return true;
            } else {
                return false;
            }
        }
        return false;
    }
    • 不能和系统值冲突

    我们在官方文档可以看到,Qt 自带的事件类的 type 值都已经在 QEvent::type 中有了,数值范围在 0 - 999 之间。

    • 自定义的不能冲突

    而我自己定义的事件类也有个 type 值,如上文的代码“static const Type type”。为了保证我的这个值不和 Qt 的冲突,所以数值要大于 999。Qt 给我们规定了两个边界值:QEvent::User 和 QEvent::MaxUser,即 1000 - 65535。

    但这也太难记了,怎么办?Qt 提供了一个函数 registerEventType() 专门用于自定义事件的注册。如下:

    const QEvent::Type MyEvent::type = (QEvent::Type)QEvent::registerEventType()

    好了,现在我们已经认识了 Qt 事件类长什么样了。接下来就是怎么把它发送出去了。

    如何发送一个事件?

    Qt 提供了三个 static 事件发送函数:sendEvent、postEvent、sendPostEvents。函数原型如下:

    bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
    void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
    void QCoreApplication::sendPostedEvents(QObject *receiver = Q_NULLPTR, int event_type = 0)

    直接发送:sendEvent

    这是最好理解的,两个参数中一个是要发给谁,另一个是发送什么事件。这种方式的发送我们要等待对方处理,所以返回值是一个 bool 量来判断。对于许多事件类,有一个名为 isAccepted() 来告诉你是否被处理。因为事件被发送的时候,event 对象并不会被销毁,因此我们要在栈上创建 event 对象。

    举个例子比较好理解:

    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        MainWindow w;
        w.show();
        
        MyEvent event(1, 2, 3);
        QCoreApplication::sendEvent(&w, &event);
        
        return a.exec();
    };

    发到队列:postEvent

    我们创建一个 Qt 程序的时候,一般在 main 下面会看到 QCoreApplication a(argc, argv) 以及 return a.exec() 的字样。这其实就是开启了 Qt 事件循环来维护一个事件队列,exec 的本质就是不停的调用 processEvent() 函数从队列中获取事件来处理。而 postEvent() 的作用就是把事件发送到这个队列中去。

    这种方式不需要等待处理结果,只要把事件发到队列中就可以了,所以返回值是 void。由于事件队列会持有发送的事件对象,在 post 的时候会将事件 delete 掉,所以我们必须在堆上创建 event 对象。

    在队列中立即处理:sendPostedEvents

    看参数我们就可以知道,这个函数的作用就是立刻、马上将队列中的 event_type 类型的事件立马交给 receiver 进行处理。需要注意的是,来自窗口系统的事件并不由这个函数进行处理,而是 processEvent()。

    如何处理一个事件?

    创建了事件,发送了事件,接下来就是怎么接收处理事件了。

    发送的事件在哪里捕获?

    我们发送的事件传给了 QObject 对象,更具体点是传给了 QObject 对象的 event() 函数。根据官方文档的说明,所有的事件都会进到这个函数里面,那么我们处理事件就要重写这个 event() 函数。

    在 event() 中怎么处理事件?

    event() 函数本身不会去处理事件,而是根据事件类型(type值)调用不同的事件处理器函数。大家都是这样的一个流程,所以我们也尽量保持统一。请看下图,其中的 timerEvent()、childEvent() 都是处理事件的函数。

    再举个简单的例子:鼠标点击一下 -> 产生鼠标点击事件 -> 接收者的 event() 函数判断类型 -> 发给 mousePressEvent() 函数处理。写成代码就是这样的:

    void MyCheckBox :: mousePressEvent( QMouseEvent *event )
    {
        if(event->button() == Qt::LeftButton)
        {
            ....//点击左键后实现的功能
        } else {
            QCheckBox::mousePressEvent(event);
        }
    }

    需要注意的是,重写事件处理器函数时,如果不实现任何功能,最好调用基类的实现。就像上面的那段代码,Qt 本身就已经写了一大堆的实现了,你要是不写上 QCheckBox::mousePressEvent(event) 这个代码,那你写的这个继承于 QCheckBox 类的 MyCheckBox 就不会对鼠标点击产生任何反应。正所谓“你要不会干这事,叫你爸爸来做吧”。

    至此,一个完整的事件处理过程已经说完了。此时的你应该不仅了解了 Qt 自带的类是如何处理事件的,而且写个自定义事件也是应该是能下手了。接下来我们对事件处理再说说其他方便的功能:过滤、接收/忽略。

    技巧一:接收/忽略事件

    所有的事件的祖先都是 QEvent 类,而该类里有个 isAccepted() 函数来判断这个事件是否被接受了。看到这里我们就能推测出 QEvent 类身上有一个私有变量,或者称为标签吧,用来指示 QEvent 是不是被人处理过了。我一个小小的事件处理没处理有啥关系?很有关系!顺序是这样的:当收到一个事件对象时,我先拿到这个事件。如果处理它,那么事情也就到此为止了;如果不去处理,它会传到我爸爸(基类)那儿去;他如果也不处理,就继续传给我的爷爷。

    事实上,我们很少会用到 accept()、ignore() 函数的,如果忽略事件(即自己不想去处理这个事件),只要调用父类的响应函数就可以了。

    一个特殊的例子:QWidget

    Qt 中大部分的事件处理函数都是 accept() 接收的事件对象,而 QWidget 却是 ignore() 的。这样的话如果某个 QWidget 子类想忽略某个事件,既可以调用 ignore() 函数,也可以调用基类 QWidget 的默认实现。另外还有一点需要知道,在图形编辑中,如果指定了父类(注意是父类,不是基类),事件的传播是在组件层面的,而不是靠类继承机制。

    用一个小例子加深事件忽略/接收

    看代码:

    class A : public QPushButton
    {
    	void mousePressEvent(QMouseEvent *e)
    	{ qDebug() << "A";}
    };
    
    class B : public A
    {
    	void mousePressEvent(QMouseEvent *e)
    	{ qDebug() << "B";}
    };
    
    class MyWidget : public QWidget
    {
    	void mousePressEvent(QMouseEvent *e)
    	{ qDebug() << "MyWidget"; }
    };
    
    MainWindow::MainWindow(QWidget *parent) :
    	QMainWindow(parent)
    {
    	MyWidget *myWidget = new MyWidget();
    	myWidget->setParent(this);
    
    	A *a = new A();
    	a->setParent(myWidget);
    	a->setText("A_Button");
    
    	B *b = new B();
    	b->setParent(myWidget);
    	b->setText("B_Button");
    }
    
    void MainWindow::mousePressEvent(QMouseEvent *e)
    {
    	qDebug() << "MainWindow";
    }

    实验1:点击 B 按钮,运行结果是“B”。因为我们重写了鼠标按下事件处理函数,并没有调用基类函数;

    实验2:将 B 的 mousePressEvent() 第一行添加“event->accept()”。运行结果不变,正如上文所述 QEvent 默认是 accept 的,调用这个函数并没有什么区别。

    实验3:将 B 的 mousePressEvent() 改成 event->ignore()。运行结果是“B MyWidget”。ignore 是作用是让事件继续传播,因此父组件 MyWidget 也收到了这个事件。而 MyWidget 没有调用基类的事件处理函数,所以事件传播到此就结束了。

    必须调用 accept()、ignore() 函数的场景

    就是窗口关闭事件!对于 QCloseEvent 来说,接收意味着事件到此结束不再传播,窗口关闭;忽略意味着事件继续传播,窗口仍然开着。

    技巧二:过滤事件

    其实上面已经就可以对事件搞点事情了,在 event() 函数中,调用处理函数前可以做些事儿。Qt 还设计了 eventFilter() 函数,所以总的来说过滤事件有两种方法,后者更为灵活。

    事件接收者是 QObject 类型的,而 QObject 类有个 eventFilter() 函数,有什么用呢?比如在主界面上有一个按钮叫“A”,有一个按钮叫“B”。当 B->installEventFilter(MainWindow) 的时候,B 产生的事件就不会进入接收者的 event() 函数中了,而是进入接收者的 eventFilter() 函数中。如下图。

    注意:如果你在事件过滤器中 delete 了某个接收组件,务必将函数返回值设为 true。否则,Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。

    ok,讲完啦~

    ============== End

  • 相关阅读:
    电脑硬盘坏了如何恢复重要的资料?
    js删除数组里的某个元素
    仅当使用了列的列表 并且 identity_insert 为 on 时 才能在表 中为标识列指定显式值
    data source 和initial catalog
    如何从一个表的数据复制到另一个表里面(二个库不在同一台电脑上)
    为什么编程语言以及数据库要从1970年1月1日开始计算时
    关于1970-1-1 00:00.000的知识
    C/S ASP.NET页面传值汇总
    SQL高级查询的练习题
    经典SQL练习题
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/12408751.html
Copyright © 2011-2022 走看看