Qt事件机制

Qt事件

  1. Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发
  2. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期

常见Qt事件类型

  • 键盘事件:按键按下和松开
  • 鼠标事件:鼠标移动,鼠标按键的按下和松开
  • 拖放事件:用鼠标进行拖放
  • 滚轮事件:鼠标滚轮滚动
  • 绘屏事件:重绘屏幕的某些部分
  • 定时事件:定时器到时
  • 焦点事件:键盘焦点移动
  • 进入和离开事件:鼠标移入widget之内,或是移出
  • 移动事件:widget的位置改变
  • 大小改变事件:widget的大小改变
  • 显示和隐藏事件:widget显示和隐藏
  • 窗口事件:窗口是否为当前窗口

非常见Qt事件类型

  • socket事件
  • 剪贴板事件
  • 字体改变事件
  • 布局改变事件

Qt将系统产生的消息转化为Qt事件,Qt事件被封装为对象,所有的Qt事件均继承抽象类QEvent,用于描述程序内部或外部发生的动作,任意的QObject对象都具备处理Qt事件的能力。
QEvent_Inheritance

事件的产生来源

系统产生

操作系统将获取的事件,如鼠标、键盘按键等事件,放入系统消息队列中,Qt事件循环的时候读取消息队列中的事件,转化为QEvent,再依次处理

Qt应用程序产生

此种产生有两种方式

调用QApplication::postEvent()

Widget需要重新绘制屏幕时,程序将调用update()函数,new出来一个paintEvent,调用 QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理

调用sendEvent()函数

事件不会放入队列,而是直接被派发和处理,QWidget::repaint()函数用的就是这种方式

Qt事件的处理

调度方式

调度方式有两种,同步与异步

异步调度

Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环,先处理Qt事件队列中的事件, 直至为空,再处理系统消息队列中的消息,直至为空,处理系统消息的时候会产生新的Qt事件,需要对其再次进行处理

下面来看看Qt事件循环

while (!app_exit_loop)
{
    while (!postedEvents)
    {
        // 由Qt应用程序产生,将放入Qt事件队列中
        processPostedEvents();
    }

  while (!qwsEvnts)
  {
        // 由窗口系统产生,将被放入系统队列中去
        qwsProcessEvents();
    }

    while (!postedEvents)
    {
        // 处理系统消息产生的新的Qt事件
        processPostedEvents();
    }
}
同步调度

调用QApplication::sendEvent()的时候,消息会立即被处理,是同步的;实际上QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节

事件的分发与处理

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

event()函数接受一个QEvent对象,这个对象是需要转发的对象,转发时需要调用type()函数进行类型判断,函数的返回值是QEvent::Type的枚举;处理完事件后,可以直接return true,这时QApplicationnotify()函数会认为这个事件已处理完毕,则会继续从处理事件队列中取出下一事件进行处理;若返回值为falseQApplication会尝试寻找这个事件的下一个处理函数

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;
}

当需要重写event()函数时,只写关于我们需要处理事件即可,其他事件调用父类的event()函数继续转发通过return parent::event(event)实现,否则这个组件就只能处理我们定义的事件了

如下的重写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);//其他事件转发给父类处理
}

事件过滤器

事件过滤器是Qt中一个独特的事件处理机制,它让一个对象监听拦截另外一个对象的事件,可在一个对象处理事件前调用,可判断是否调用event()函数,主要用于过滤事件,例如停止对某事件的响应

一个对象可以给多个对象安装过滤器,一个对象能同时被安装多个过滤器,在事件到达之后,事件过滤器以安装次序的反序被调用,类似于栈操作

事件过滤器函数eventFilter()返回值是bool型, 如果返回true,则表示事件已经被处理完毕,Qt将直接返回,进行下一事件的处理;如果返回false,事件将接着被送往剩下的事件过滤器或是目标对象进行处理

事件过滤器作用过程

事件过滤器可以对其他组件接收到的事件进行监控,任意的QObject对象都可以作为事件过滤器使用,事件过滤器对象需要重写eventFilter函数

组件通过installEventFilter函数安装事件过滤器,事件过滤器在组件之前接收到事件,能够决定是否将事件转发到组件对象

eventFilter_Flowchat

事件过滤器的具体实现
  1. 在所有Qt对象的基类QObject中有一个类型为QObjectList的成员变量,名字为eventFilters;
  2. 当A对象给B对象安装了事件过滤器后,B会把A的指针保存在eventFilters
  3. 在B处理事件前,会先去检查eventFilters列表
  4. 如果非空,就先调用列表中对象的eventFilter()函数
  5. 函数执行完后,根据返回值确定是否需要再次执行此事件
  6. 返回true则不会再次执行此事件,否则继续执行此事件
建立事件过滤器

函数声明

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

watched对象安装了事件过滤器,这个函数会调用并进行事件过滤,然后才进行组件事件处理,重写这个函数时,若想过滤掉某事件,例停止某事件的响应,需要返回true,对于该组件其他需要处理的事件则返回false,而对于其他组件的事件,并不能保证它有没有安装过滤器,则需要转发给父类

例子如下

bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == textEdit) 
    {
        if (event->type() == QEvent::KeyPress) 
        {
            return true;
        } 
        else 
        {
            return false;
        }
    } 
    else 
    {
    // pass the event on to the parent class
        return QMainWindow::eventFilter(obj, event);
    }
}

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

安装过滤器

函数声明如下:

void QObject::installEventFilter(QObject * filterObj)

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

事件过滤器和被安装的组件必须在同一线程

事件的转发

对于某些类别的事件,如果在整个事件的派发过程结束后还没有被处理,那么这个事件将会向上转发给它的父widget,直到最顶层窗口

事件之间的相互通信

  1. QApplication::notify()QObject::eventFilter()QObject::event()通过返回bool值来表示是否已处理
  2. 调用QEvent::ignore()QEvent::accept()对事件进行标识,只用于event()函数和特定事件处理函数之间的沟通,而且只有用在某些类别事件上是有意义的看,这些事件就是上面提到的那些会被转发的事件,包括鼠标,滚轮,按键等事件

事件的处理、过滤

QT提供了五种不同级别的事件处理和过滤

重写特定事件处理函数

最常见的事件处理办法就是重写mousePressEvent()keyPressEvent()paintEvent()等特定事件处理函数

void imageView::keyPressEvent(QKeyEvent * event)
{
    switch (event->key()) 
    {
    case Key_Plus:
        zoomIn();
        break;
    case Key_Minus:
        zoomOut();
        break;
    case Key_Left:
        // …
    default:
        QWidget::keyPressEvent(event);
    }
}

重写event()函数

重写event()函数时,需要调用父类的event()函数来处理不需要处理或是不清楚如何处理的事件

下面这个例子演示了如何重载event()函数, 改变Tab键的默认动作

bool CodeEditor::event(QEvent * event)
{
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = (QKeyEvent *) event;
        if (keyEvent->key() == Key_Tab)
        {
            insertAtCurrentPosition('\t');
            return true;
        }
    }
    return QWidget::event(event);
}

Qt对象安装事件过滤器

安装事件过滤器有两个步骤(假设要用A来监视过滤B的事件)

  1. 首先调用B的installEventFilter(const QOject* obj),以A的指针作为参数,这样所有发往B的事件都将先由A的eventFilter()处理
  2. A要重载QObject::eventFilter()函数, 在eventFilter()中写对事件进行处理的代码

用这种方法改写上面的例子(假设我们将CodeEditor放在MainWidget中)

MainWidget::MainWidget()
{
    CodeEditor* ce = new CodeEditor(this, “code editor”);
    ce->installEventFilter(this);
}

bool MainWidget::eventFilter(QOject* target, QEvent* event)
{
    if(target == ce)
    {
        if(event->type() == QEvent::KeyPress)
        {
            QKeyEvent* key = (QKeyEvent*)event;
            if(key->key() == Key_Tab)
            {
                ce->insertAtCurrentPosition('\t');
                return true;
            }
        }
    }
    return false;
}

QAppliction对象安装事件过滤器

如果给QApplication对象装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前eventFilter()QApplication::notify()中, 是先调用qApp的过滤器,再对事件进行分析,以决定是否合并或丢弃

继承QApplication类,并重载notify()函数

Qt是用QApplication::notify()函数来分发事件的,要在任何事件过滤器查看任何事件之前先得到这些事件,重写notify()函数是唯一的办法;
通常来说事件过滤器更好用一些,因为不需要去继承QApplication类,而且可以给QApplication对象安装任意个数的事件过滤器

GUI应用程序的事件处理

  • Qt事件产生后会被立即发送到QWidget对象
  • QWwidget中的event(QEvent*)进行事件处理
  • event(QEvent*)根据事件类型调用不同的事件处理函数
  • 在事件处理函数中发送Qt预定义的信号
  • 调用信号关联的槽函数

QEvent_Flowchat

自定义事件与事件处理

定制某个组件的事件处理

处理流程
  1. 确定对组件的哪些事件进行处理,如closekeykeyboard事件等
  2. 继承QEvent并重写对象的event()函数

QEvent中的主要成员函数

void ignore();// 接收者忽略当前事件,事件可能传递给父组件
void accept();// 接收者期望处理当前事件
bool isAccepted();// 判断当前事件是否被处理
避免自定义事件覆盖系统事件

继承QEvent类,还需要提供一个QEvent::Type类型的参数,作为自定义事件的类型值,这是Qt事件类型的枚举,在Qt中,系统保留了0-999的值,故自定义事件的值要大于999,即自定义事件的值要在QEvent::UserQEvent::MaxUser之间,即1000-65535,这能保证自定义事件不会覆盖系统事件

避免自定义事件之间的相互覆盖

要想保证自定义事件之间不会被覆盖,则需要通过registerEventType()函数注册自定义事件

函数声明如下

static int QEvent::registerEventType(int hint = -1);

若函数接受一个int值,若hint合法,即不会发生任何覆盖,则会返回hint,若不合法,系统会自动分配一个合法值并返回

事件过滤流程

  1. 确定需要过滤处理哪些对象的哪些事件
  2. 构造自己的事件过滤类:重写类的eventFilter函数
  3. 在主程序中实例化一个过滤类对象
  4. 调用过滤类对象的installEventFilter(receiver, QEvent* event)函数,在目标对象上安装过滤器

事件的发送

sendEvent()发送方式

sendEvent()立即同步处理要发送的event,对于多数的event类,有一个成员函数isAccepted()可用来判别event事件是已被接受处理或被拒绝处理;使用这个函数必须栈上创建对象

函数声明

static bool QCoreApplication::sendEvent(QObjecy receiver, QEvent event)

发送过程

  1. 构造事件对象

    QEvent event(QEvent::MouseButtonPress);

  2. 发送事件给指定对象

    QApplication::sendEvent(receive, &event);

postEvent()发送方式

postEvent()event提交到一个事件队列中等待调度,在下一次Qt的主event loop运行的时候,主event loop就会以某种优化的方式调度所有提交到队列中的event;例如, 如果有几个 resize event,他们就会被压缩成一个事件

postEvent()也被用于对象的初始化过程,因为提交过的event 通常在相应对象初始化完毕后极短的时间内就会被调度;使用这个函数必须在堆上创建对象

函数声明

static bool QCoreApplication::postEvent(QObject receiver, QEvent event)

发送过程

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

这个对象不需要手动deleteQt会自动delete

事件与信号的区别

信号通常用来”使用”widget,而事件用来”实现”widget

事件由具体对象进行处理,信号由具体对象产生

所有信号的实现都是通过事件来实现的,例如,一个buttonclicked()信号,如何处理鼠标事件再发射这个信号是由事件完成的

事件可以过滤,事件更底层,事件是基础,信号是扩展

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值