Qt事件
- Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发
- Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期
常见Qt事件类型
- 键盘事件:按键按下和松开
- 鼠标事件:鼠标移动,鼠标按键的按下和松开
- 拖放事件:用鼠标进行拖放
- 滚轮事件:鼠标滚轮滚动
- 绘屏事件:重绘屏幕的某些部分
- 定时事件:定时器到时
- 焦点事件:键盘焦点移动
- 进入和离开事件:鼠标移入widget之内,或是移出
- 移动事件:widget的位置改变
- 大小改变事件:widget的大小改变
- 显示和隐藏事件:widget显示和隐藏
- 窗口事件:窗口是否为当前窗口
非常见Qt事件类型
- socket事件
- 剪贴板事件
- 字体改变事件
- 布局改变事件
Qt将系统产生的消息转化为Qt事件,Qt事件被封装为对象,所有的Qt事件均继承抽象类QEvent
,用于描述程序内部或外部发生的动作,任意的QObject
对象都具备处理Qt事件的能力。
事件的产生来源
系统产生
操作系统将获取的事件,如鼠标、键盘按键等事件,放入系统消息队列中,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就将这个事件传递给QObject
的event()
函数,它并不直接处理事件,而是将事件按照不同类型分发给不同的事件处理器event handler
event()
函数接受一个QEvent
对象,这个对象是需要转发的对象,转发时需要调用type()
函数进行类型判断,函数的返回值是QEvent::Type
的枚举;处理完事件后,可以直接return true
,这时QApplication
的notify()
函数会认为这个事件已处理完毕,则会继续从处理事件队列中取出下一事件进行处理;若返回值为false
,QApplication
会尝试寻找这个事件的下一个处理函数
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
函数安装事件过滤器,事件过滤器在组件之前接收到事件,能够决定是否将事件转发到组件对象
事件过滤器的具体实现
- 在所有Qt对象的基类
QObject
中有一个类型为QObjectList
的成员变量,名字为eventFilters
; - 当A对象给B对象安装了事件过滤器后,B会把A的指针保存在
eventFilters
中 - 在B处理事件前,会先去检查
eventFilters
列表 - 如果非空,就先调用列表中对象的
eventFilter()
函数 - 函数执行完后,根据返回值确定是否需要再次执行此事件
- 返回
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,直到最顶层窗口
事件之间的相互通信
QApplication::notify()
,QObject::eventFilter()
,QObject::event()
通过返回bool值来表示是否已处理- 调用
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的事件)
- 首先调用B的installEventFilter(const QOject* obj),以A的指针作为参数,这样所有发往B的事件都将先由A的
eventFilter()
处理 - 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
预定义的信号 - 调用信号关联的槽函数
自定义事件与事件处理
定制某个组件的事件处理
处理流程
- 确定对组件的哪些事件进行处理,如
close
、key
、keyboard
事件等 - 继承
QEvent
并重写对象的event()
函数
QEvent
中的主要成员函数
void ignore();// 接收者忽略当前事件,事件可能传递给父组件
void accept();// 接收者期望处理当前事件
bool isAccepted();// 判断当前事件是否被处理
避免自定义事件覆盖系统事件
继承QEvent
类,还需要提供一个QEvent::Type
类型的参数,作为自定义事件的类型值,这是Qt
事件类型的枚举,在Qt
中,系统保留了0-999
的值,故自定义事件的值要大于999,即自定义事件的值要在QEvent::User
与QEvent::MaxUser
之间,即1000-65535
,这能保证自定义事件不会覆盖系统事件
避免自定义事件之间的相互覆盖
要想保证自定义事件之间不会被覆盖,则需要通过registerEventType()
函数注册自定义事件
函数声明如下
static int QEvent::registerEventType(int hint = -1);
若函数接受一个int
值,若hint
合法,即不会发生任何覆盖,则会返回hint
,若不合法,系统会自动分配一个合法值并返回
事件过滤流程
- 确定需要过滤处理哪些对象的哪些事件
- 构造自己的事件过滤类:重写类的
eventFilter
函数 - 在主程序中实例化一个过滤类对象
- 调用过滤类对象的
installEventFilter(receiver, QEvent* event)
函数,在目标对象上安装过滤器
事件的发送
sendEvent()
发送方式
sendEvent()
立即同步处理要发送的event
,对于多数的event
类,有一个成员函数isAccepted()
可用来判别event
事件是已被接受处理或被拒绝处理;使用这个函数必须栈上创建对象
函数声明
static bool QCoreApplication::sendEvent(QObjecy receiver, QEvent event)
发送过程
构造事件对象
QEvent event(QEvent::MouseButtonPress);
发送事件给指定对象
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)) );
这个对象不需要手动delete
,Qt
会自动delete
事件与信号的区别
信号通常用来”使用”widget
,而事件用来”实现”widget
事件由具体对象进行处理,信号由具体对象产生
所有信号的实现都是通过事件来实现的,例如,一个button
的clicked()
信号,如何处理鼠标事件再发射这个信号是由事件完成的
事件可以过滤,事件更底层,事件是基础,信号是扩展