QT学习 之 事件与事件过滤器

在Qt中,事件是作为对象处理的,所有事件对象继承自抽象类QEvent。此类用来表示程序内部发生或者来自于外部但应用程序应该知道的动作。事件能够能过被 QObject 的子类接受或者处理,但是通常用在与组件有关的应用中。本文主要阐述了在一个典型应用中的事件接收与处理。

事件的传递发送

当一个事件产生时,Qt 通过实例化一个 QEvent 的合适的子类来表示它,然后通过调用 event() 函数发送给 QObject 的实例(或者它的子类)。 event() 函数本身并不会处理事件,根据事件类型,它将调用相应的事件处理函数,并且返回事件被接受还是被忽略。 一些事件,比如 QMouseEvent 和 QKeyEvent,来自窗口系统;有的,比如 QTimerEvent,来自于其他事件源;另外一些则来自应用程序本身。

事件的类型

大部分事件类型有专门的类,比如 QResizeEvent, QPaintEvent, QMouseEvent, QKeyEvent 和 QCloseEvent。它们都是 QEvent 的子类,并且添加了自己特定的事件处理函数。比如 QResizeEvent 事件添加了 size()和 oldSize() 函数,使组件获知自身大小的改变。

有些事件支持不止一个事件类型。比如 QMouseEvent 鼠标事件,可以表示鼠标的按下,双击,移动,以及其它的一些操作。

每一个事件都有其相关联的类型,由 QEvent::Type 定义。我们能够很方便地在运行时用这些类型来判断该事件是哪一个子类。

因为程序响应方式的多样性和复杂性,Qt 的事件传递机制是富有弹性很灵活的。QCoreApplication::notify() 的相关文档阐述大部分内容;Qt Quarterly 中的文章 Another Look at Events 也进行了简要描述。在这里我们的阐述对于 95% 的程序而言来说已经足够了。

事件的处理

通常事件的处理需要调用一个虚函数。比如,QPaintEvent 事件的处理需要调用 QWidget::paintEvent() 函数。这个虚函数负责做出适当的响应,通常是用来重绘组件。如果你在自己的函数中并不打算实现所有的处理,你可以调用基类的实现。 例如,下面的代码用来处理鼠标左键点击一个自定义的选择框的操作,而其他的点击事件则被传递给基类 QCheckBox 处理。

void MyCheckBox::mousePressEvent(QMouseEvent *event){if (event->button() == Qt::LeftButton) {// handle left mouse button here} else {// pass on other buttons to base classQCheckBox::mousePressEvent(event);}}

如果你想代替基类的处理,你必须自己实现所有的功能。但是,如果你只想扩展子基类的功能,你只需要实现你自己需要的那部分,剩下的让基类来替你处理。 少数情况下,Qt 可能没有指定专门的处理函数,或者指定的处理函数不能满足要求。通常对 Tab 键的处理就会发生这种情况。一般地,Tab 键用来移动焦点,但是一些控件需要 Tab 键作其它的事情。 这些对象可以通过重新实现 QObject::event() 来满足需要,它们可以在通用处理调用之前或之后来加入自己的处理,或者完全将事件处理替换为自己的事件处理函数。一个非常罕见的控件或许既要处理 Tab 键,又要调用程序特定的事件类型。那么,我们就可以使用以下代码实现。

bool MyWidget::event(QEvent *event){if (event->type() == QEvent::KeyPress) {QKeyEvent *ke = static_cast<QKeyEvent *>(event);if (ke->key() == Qt::Key_Tab) {// special tab handling herereturn true;}} else if (event->type() == MyCustomEventType) {MyCustomEvent *myEvent = static_cast<MyCustomEvent *>(event);// custom event handling herereturn true;}return QWidget::event(event);}

注意,QWidget::event() 在那些没有被处理的事件仍然要被调用,并且通过返回值表示事件是否被处理,返回 true 表示事件被阻止发送到其他的对象。

事件过滤器

有时,并不存在一个特定事件函数,或者特定事件功能不足。最普通的例如按下tab键。正常情况下,被QWidget看成是去移动 键盘焦点,但少数窗口部件需要自行解释。 让我们试着设想已经有了一个CustomerInfoDialog的小部件。CustomerInfoDialog 包含一系列QLineEdit. 现在,我们想用空格键来代替Tab,使焦点在这些QLineEdit间切换。 一个解决的方法是子类化QLineEdit,重新实现keyPressEvent(),并在keyPressEvent()里调用focusNextChild()。像下面这样:

void MyLineEdit::keyPressEvent(QKeyEvent *event) {if (event->key() == Qt::Key_Space){focusNextChild();}else{QLineEdit::keyPressEvent(event);} }

但这有一个缺点。如果CustomerInfoDialog里有很多不同的控件(比如QComboBox,QEdit,QSpinBox),我们就必须子类化这么多控件。这是一个烦琐的任务。 一个更好的解决办法是: 让CustomerInfoDialog去管理他的子部件的按键事件,实现要求的行为。我们可以使用事件过滤器。

一个事件过滤器的安装需要下面2个步骤: 1, 调用installEventFilter()注册需要管理的对象。 2,在eventFilter() 里处理需要管理的对象的事件。

一般,推荐在CustomerInfoDialog的构造函数中注册被管理的对象。像下面这样:

CustomerInfoDialog::CustomerInfoDialog(QWidget *parent): QDialog(parent){…firstNameEdit->installEventFilter(this);lastNameEdit->installEventFilter(this);cityEdit->installEventFilter(this);phoneNumberEdit->installEventFilter(this);}

一旦,事件管理器被注册,发送到firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit的事件将首先发送到eventFilter()。

下面是一个 eventFilter()函数的实现:

bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event) {if (target == firstNameEdit || target == lastNameEdit|| target == cityEdit || target == phoneNumberEdit){if (event->type() == QEvent::KeyPress){QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);if (keyEvent->key() == Qt::Key_Space){focusNextChild();return true;}}}return QDialog::eventFilter(target, event); }如若今生再相见,哪怕流离百世,迷途千年,也愿。

QT学习 之 事件与事件过滤器

相关文章:

你感兴趣的文章:

标签云: