事件

一、 事件

1 . 事件(event) 是由系统或者 Qt 本身在不同的时刻发出的。 当用户按下鼠标、 敲下键盘, 或者是窗口需要重新绘制的时候, 都会发出一个相应的事件。 一些事件在对用户操作做出响应时发出, 如键盘事件等; 另一些事件则是由系统自动发出,如计时器事件。

  • 在前面我们也曾经简单提到, Qt 程序需要在 main()函数创建一个QApplication 对 象, 然后调用它的exec()函数
  • 这个函数就是开始 Qt 的事件循环
  • 执行 exec() 函数之后,程序将进入事件循环来监听应用程序的事件
  • 事件发生时, Qt 将 创建一个事件对象
  • Qt 中所有事件类都继承于 QEvent
  • 事件对象创建完毕后, Qt 将这个事件对象传递给 QObject 的 event()函数
  • event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)

在所有组件的父类 QWidget 中, 定义了很多事件处理的回调函数, 如:

keyPressEvent()
keyReleaseEvent()
mouseDoubleClickEvent()
mouseMoveEvent()
mousePressEvent()
mouseReleaseEvent()

这些函数都是 protected virtual 的, 也就是说, 我们可以在子类中重新实现这些函数

2. mousePressEvent函数:

[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event)

作用:这个事件处理程序(用于事件事件)可以在子类中重新实现,以接收小部件的鼠标按下事件。如果您在mousePressEvent()中创建新的小部件,那么mouseReleaseEvent()可能不会在您期望的地方结束,这取决于底层小部件类型,它什么也不做

3. mouseReleaseEvent函数:

[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event)

作用:这个事件处理程序(用于事件事件)可以在子类中重新实现,以接收小部件的鼠标释放事件

4. mouseMoveEvent函数

[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event)

作用:这个事件处理程序(用于事件事件)可以在子类中重新实现,以接收小部件的鼠标移动事件如果关闭鼠标跟踪,鼠标移动事件仅在鼠标移动时按下鼠标按钮时发生。如果打开鼠标跟踪,即使没有按下鼠标按钮,也会发生鼠标移动事件

5.Qt的sprintf

QString 的 arg()函数可以自动替换掉 QString 中出现的占位符。 其占位符以 %
开始, 后面是占位符的位置,

例如 %1, %2 这种。

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

语句将会使用 x 替换 %1, y 替换 %2, 因此, 生成的 QString 为[x, y]。

QString str = QString("i am %1;i age %2").arg("wolf").arg(22);
    qDebug()<<str;

6.enterEvent函数:

[virtual protected] void QWidget::enterEvent(QEvent *event)

作用:可以在子类中重新实现此事件处理程序来接收小部件输入事件,这些事件在事件参数中传递。当鼠标光标进入小部件时,一个事件被发送到小部件。

7.leaveEvent函数:

[virtual protected] void QWidget::leaveEvent(QEvent *event)

作用:可以在子类中重新实现此事件处理程序,以接收在事件参数中传递的小部件休假事件。当鼠标离开小部件时,将向小部件发送leave事件。

8.QMouseEvent

头文件
#icnlude <QMouseEvent>

9.keyPressEvent函数

[virtual protected] void QWidget::keyPressEvent(QKeyEvent *event)

作用:这个事件处理程序(用于事件事件)可以在子类中重新实现,以接收小部件的按键事件

10.keyReleaseEvent函数:

[virtual protected] void QWidget::keyReleaseEvent(QKeyEvent *event)

作用:这个事件处理程序(用于事件事件)可以在子类中重新实现,以接收小部件的键释放事件。

11.完整代码:

protected:
	void mousePressEvent(QMouseEvent *ev);
    void mouseReleaseEvent(QMouseEvent *ev);
    void mouseMoveEvent(QMouseEvent *ev);
    void enterEvent(QEvent *event);
    void leaveEvent(QEvent *event);
    void keyPressEvent(QKeyEvent *event);
    void keyReleaseEvent(QKeyEvent *event);
    void timerEvent(QTimerEvent *event);
    int id;
    int timeid;
#include "mylabel.h"
#include <QMouseEvent>
#include <QDebug>

MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
	//设置追踪鼠标,一进对话框就有效
    setMouseTracking(true);
}

//鼠标点击事件
void MyLabel::mousePressEvent(QMouseEvent *ev)
{
	//获取xy坐标
    int i = ev->x();
    int j = ev->y();
	
	//判断点击的类型
    if(ev->button() == Qt::LeftButton)
    {
        qDebug()<<"i am left";
    }
    else if(ev->button() == Qt::RightButton)
    {
        qDebug()<<"i am right";
    }
    else if(ev->button() == Qt::MidButton)
    {
        qDebug()<<"i am mid";
    }
	
	//在对话框上显示内容
    QString text = QString("<center><h1>Mouse Press : (%1,%2)</h1></center>").arg(i).arg(j);
    this ->setText(text);
}

//鼠标抬起事件
void MyLabel::mouseReleaseEvent(QMouseEvent *ev)
{
	//设定内容
    QString text = QString("<center><h1>Mouse Release : (%1,%2)</h1></center>").arg(ev->x()).arg(ev->y());
    this ->setText(text);
}

//鼠标移动事件
void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
    QString text = QString("<center><h1>Mouse Move : (%1,%2)</h1></center>").arg(ev->x()).arg(ev->y());
    this ->setText(text);

}

//鼠标进入控件事件
void MyLabel::enterEvent(QEvent *event)
{
    QString text = QString("<center><h1>Mouse enter window</h1></center>");
    this ->setText(text);
}

//鼠标离开控件事件
void MyLabel::leaveEvent(QEvent *event)
{
    QString text = QString("<center><h1>Mouse leave window</h1></center>");
    this ->setText(text);
}

//鼠标按下按键事件
void MyWidget::keyPressEvent(QKeyEvent *event)
{
    //qDebug()<<(char)event->key();
    //判断用户按下的是什么按键
    if(event->key() == Qt::Key_A)
    {
        qDebug()<<"i am A";
    }
    else if(event->key() == Qt::Key_Shift)
    {
        qDebug()<<"i am shift";
    }
    else if(event->key() == Qt::Key_0)
    {
        qDebug()<<"i am key_0";
    }
}

//鼠标抬起按键事件
void MyWidget::keyReleaseEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_A)
    {
        qDebug()<<"i am A";
    }
    else if(event->key() == Qt::Key_Shift)
    {
        qDebug()<<"i am shift";
    }
    else if(event->key() == Qt::Key_0)
    {
        qDebug()<<"i am key_0";
    }
}

//设定定时器事件
void MyWidget::timerEvent(QTimerEvent *event)
{
	//对不同的定时器进行区分
    if(event->timerId() == id)
    {
        static int i = 0;
        i++;
        ui->label->setText(QString("<center><h1>timeout : [%1]</h1></center>").arg(i));

        if(i == 6)
        {
            killTimer(id);
        }

    }
    else if (event->timerId() == timeid)
    {
        static int i = 0;
        i++;
        ui->label_2->setText(QString("<center><h1>timeout : [%1]</h1></center>").arg(i));

        if(i == 6)
        {
            killTimer(timeid);
        }
    }
}

//事件的传递和忽略
void MyWidget::closeEvent(QCloseEvent *event)
{
	//建造一个问题对话框
    int ret = QMessageBox::question(this,"close","are you sure");
    //如果按下的是YES就关闭窗口
    if(ret == QMessageBox::Yes)
    {
        qDebug()<<"i am closed window";
        //事件不会往下传
        event->accept();
    }
     else if(ret == QMessageBox::No)
     {
         qDebug()<<"i am no close window";
         //不会关闭窗口,事件会传递给父组件
         event->ignore();
     }
}

二、event

1.event函数

[virtual protected] bool QWidget::event(QEvent *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, 那么 Qt会认为这个事件已经处理完毕, 不会再将这个事件 发送给其它对象, 而是会继续处理事件队列中的下一事件
  • 在 event()函数中, 调用事件对象的 accept()和 ignore()函数是没有作用的, 不会影响到事件的传播

2. 我们可以通过使用 QEvent::type()函数可以检查事件的实际类型

其返回值是QEvent::Type 类型的枚举。 我们处理过自己感兴趣的事件之后, 可以直接返回 true,表示我们已经对此事件进行了处理; 对于其它我们不关心的事件, 则需要调用父类的 event()函数继续转发, 否则这个组件就只能处理我们定义的事件了。 为了测试这一种情况, 我们可以尝试下面的代码:

bool CustomTextEdit::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 false;
}

CustomTextEdit 是 QTextEdit 的一个子类。 我们重写了其 event()函数, 却没有调用父类的同名函数。 这样, 我们的组件就只能处理 Tab 键, 再也无法输入任何文
本, 也不能响应其它事件, 比如鼠标点击之后也不会有光标出现。 这是因为我们
只处理的 KeyPress 类型的事件, 并且如果不是 KeyPress 事件, 则直接返回 false,
鼠标事件根本不会被转发, 也就没有了鼠标事件。

3. 通过查看 QObject::event()的实现, 我们可以理解, event()函数同前面的事件处理器有什么联系

//!!! Qt5
bool QObject::event(QEvent *e)
{
	switch (e->type()) {
		case QEvent::Timer:
			timerEvent((QTimerEvent*)e);
			break;
		case QEvent::ChildAdded:
		case QEvent::ChildPolished:
		case QEvent::ChildRemoved:
			childEvent((QChildEvent*)e);
			break;
			// ...
		default:
			if (e->type() >= QEvent::User) {
				customEvent(e);
				break;
			} 
			return false;
	} 
	return true;
}

这是 Qt 5 中 QObject::event()函数的源代码(Qt 4 的版本也是类似的) 。 我们可以看到, 同前面我们所说的一样, Qt 也是使用 QEvent::type()判断事件类型, 然后调用了特定的事件处理器。 比如, 如果 event->type()返回值是 QEvent::Timer, 则调用 timerEvent()函数。 可以想象, QWidget::event()中一定会有如下的代码

switch (event->type()) {
	case QEvent::MouseMove:
		mouseMoveEvent((QMouseEvent*)event);
		break;
		// ...
}

事实也的确如此。 timerEvent()和 mouseMoveEvent()这样的函数, 就是我们所说的事件处理器 event handler。 也就是说, event()函数中实际是通过事件处
理器来响应一个具体的事件。 这相当于 event()函数将具体事件的处理“委托”给具
体的事件处理器。 而这些事件处理器是 protected virtual 的, 因此, 我们重写了
某一个事件处理器, 即可让 Qt 调用我们自己实现的版本。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐