【QT】入门阶段之事件

本小节内容为《QTCreator快速入门》的事件笔记。包含事件关系图、鼠标、键盘、定时器等事件以及事件过滤器、事件的发送等。

1 基础概念

1.1 事件关系图

图片名称

1.2 事件

  1. 事件对象:每个事件都会包装成一个QEvent对象,然后传递到对应部件。
  2. 在Qt中,任何QObject子实例都可以接受和处理事件。意思就是,每个QObject部件都可以对事件进行拦截和处理。
  3. 处理事件的5种方法:
    图片名称

上述5种处理事件的方法,实际上对应着在事件传递的不同阶段进行拦截处理的过程。
QApplication类最后执行exec()进入事件循环,而后监听事件的发生。一旦有事件发生,Qt便会构建一个相应QEvent子类对象来表示它,将其传递给相应的QObject对象或者其子对象。
上述处理事件的5种方法种,notify相关的两种方法相当于是顶级处理,事件刚一发生就直接被QApplication捕获,从而使用notify处理(对应方法2和方法3)。如果前面的不拦截,事件会经过父部件达到子部件,父部件可以选择拦截(FilterEvent参数有两个分别是子对象以及事件类型),如果父部件不拦截,则到达子部件,子部件可以使用event()判断是哪种类型的event而后处理,如果不使用event,那么接下来就能够使用对应的某种事件的处理函数如KeyPressEvent(),如果子部件没有处理,那么事件的处理权就传递到了父部件。事件发生后,事件先传递给焦点部件,传递过程中可使用notify或者父部件进行拦截,传递到焦点部件后可选择处理,如果不处理则事件处理权转交到父部件。

1.3 事件的传递

事件是先传递给指定窗口部件的,就是说先传递给获得焦点的窗口部件,但是如果该部件忽略掉该事件,那么这个事件就会传递给这个部件的父部件。重新实现事件处理函数时,一般要调用父类的相应事件处理函数来实现默认操作。

图片名称

任何部件都可以用事件处理函数如KeyPressEvent(),此外,子部件使用bool event(QEvent),父部件使用bool eventFilter(QObject, QEvent*),并将在父部件的构造函数内安装子部件的过滤器。不要忘记重新调用父类的操作函数。

2 鼠标总结

mousePressEvent
mouseReleaseEvent
mouseDoubleClickEvent
mouseMoveEvent
wheelEvent

需要总结的是光标的设定、鼠标的移动处理、滑轮的滑动以及事件的判定。

2.1 光标的设定

光标需要初始设定,可以在窗口的构造函数中设定:

QCursor cursor;
//QCursor cursor(QPixmap(":/image/images/logo.png"))// 初始化时使用图片,引用的是资源中的图片,路径开始要用冒号:
cursor.setShap();//设定形状
setCursor(cursor);为当前的窗口设定光标的形状

光标临时设定于恢复函数,要设定为栈上的临时变量,并使用QApplication::setOverrideCursor(cursor)来暂时修改光标的形状,使用QApplication::restoreOverrideCursor()来恢复光标的形状。

2.2 鼠标的移动处理

通过按下左键并移动鼠标来拖动窗口,需要计算窗口移动后的位置来设定move。
移动后的位置=鼠标当前位置-鼠标与窗口的相对位置;此相对位置在鼠标每次点击左键的时候需要不断更新。
offset = event->globalPos()-pos(); 鼠标全局位置-窗口全局位置 = 相对距离
widgetNewPos = event->globalPos() - offset;鼠标新位置-相对距离 = 窗口新位置

2.3 滑轮的滑动

滑轮滑动事件event提供delta属性,event->delta()>0表示往前滑动,否则往后滑动。
textEdit的ZoomIn表示放大,Zoomout表示缩小。

2.4 事件的判定

鼠标点击通过event->button()==Qt::LeftButton判断是否是左键按下
鼠标的移动就需要通过当下鼠标的状态来判断是左键按下鼠标移动还是其他,通过event->buttons() & Qt::LeftButton

3 键盘总结

此处键盘的笔记包含通过事件判断按键(辅助按键、普通按键)、持续按键响应以及避免、两个普通按键组合执行动作。

3.1 键盘函数总结

事件相关函数 功能
void keyPressEvent(QKeyEvent* event) 按键按下事件
void keyReleaseEvent(QKeyEvent* event) 按键释放事件
按键相关函数 功能
event->key() 通过事件获取普通按键
event->modifiers() 通过事件获取辅助按键,Shift、Ctrl等
event->isAutoReapeat() 是否处于持续按下状态

3.2 持续按键响应

在按键事件中(Press、Release),常常使用单个按键或者组合按键,组合按键常常是辅助按键和普通按键的组合,这种情况只需要在判断一个按键的基础上,再判断另外一个,因为各自有各自的检测函数。
但是如果是两个普通按键的组合,常常需要状态变量作为标志,同时还要防止持续按下带来的响应问题。当持续按下时,Press和Release事件都会响应,event->isAutoRepeat()函数就是检测是否处于持续响应的状态。

  • event->isAutoRepeat()
    在按下时,在Press事件中此函数对第一次按下动作返回false,而对后续的持续按下返回true;在释放时,在Release中此函数对持续按下返回true,而对最后释放的动作返回false。
    因此我们可以利用此函数来捕获第一次按下的动作,以及最后释放的动作,而将持续按下的Press和Release的响应忽略掉。

3.3 实例

此实例是为了实现以下功能:按左键,按钮左移;按上键,按钮上移;按下键,按钮下移;同时按左上键,左上移。
在此实例中,第一点是使用event->isAutoRepeat()来消除持续按键响应,第二点是使用标志来记录两个普通按键的状态,第三点是时时刻刻想着只有释放的时候才会动作。


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    move = false;
    keyup = false;
    keyleft = false;
    ui->pushButton->move(400, 200);
    setFocus();
}

void Widget::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Up)
    {
        if(event->isAutoRepeat())return;
        keyup = true;
    }
    else if(event->key() == Qt::Key_Left)
    {
        if(event->isAutoRepeat()) return;
        keyleft = true;
    }
}

void Widget::keyReleaseEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Up)
    {
        if(event->isAutoRepeat()) return;
        keyup = false;
        if(move)//已经释放过了,按钮已经移动过了
        {
            move = false;
            return;
        }
        if(keyleft)
        {
            //最后一次释放,还没有移动过,并且左键已经按下了,说明现在需要斜方向移动
            ui->pushButton->move(100, 50);
            move = true;
        }
        else
        {
            ui->pushButton->move(400, 50);
        }

    }else if(event->key() == Qt::Key_Left)
    {
        if(event->isAutoRepeat()) return;//一直在持续按键,但是我需要捕捉释放的最后一次响应
        keyleft = false;
        if(move)
        {
            move = false;
            return;
        }
        if(keyup)
        {
            ui->pushButton->move(100, 50);
            move = true;
        }
        else
        {
            ui->pushButton->move(100, 200);
        }
    }else if(event->key() == Qt::Key_Down)
    {
        ui->pushButton->move(400, 200);
    }
}

/*
1. 按下去、再释放才能够执行动作
2. 释放时要判断是否是单独按下,还是已经有配合按键已经按下
3. 对于配合键:当持续按下一个键Key1的时候,press和release的Key1判断都会响应,因此应该将持续响应屏蔽掉。按下时只对第一次按下有作用,
释放时,应该忽略掉持续按下导致的响应。也就是说,我们应该捕捉按下的第一次动作,后续的都不算,应该捕捉释放的第一个动作,前面的都不算。
4. event->isAutoRepeat()函数能够起到作用。在第一次按下时press为false,在持续按下的时候press和release都是true,而释放时,release为false。
因此可以利用此函数来忽略持续响应导致的问题。
5. 两个配合按键组合成一个动作,考虑先后释放问题。哪个先释放就会响应,那么后释放的就不能再次导致响应了,因此需要加入判别标志move。第一个按键释放后,
move设置为true,第二个按键释放时发现move为true,直接返回,不再响应。

*/

3.4 注意

在构造函数中,必须使用setFocus(),暂时不知道什么用,但是还得必须用。暂时放在这里。

4 定时器事件

可以直接使用QObject的startTimer,与timerEvent关联起来。但是编程时更加常用的是QTimer类,其能够与信号、槽等创建联系,使用更加灵活。

4.1 简单的定时器

  • 开启定时器:int QObject::startTimer(int interval)
    QObject可开启定时器,输入为毫秒,返回定时器编号。定时器溢出可在timerEvent()函数中操作,在timerEvent()函数中使用event->timerId()来获取溢出的定时器ID。
    startTimer以interval为周期不断重复触发事件。

  • 定时器事件:void timerEvent(QTimerEvent* event)

    id1 = startTimer(1000);
    void Widget::timerEvent(QTimerEvent *event)
    {
    	if(event->timerId() == id1)
    	{
    		qDebug() << "id1" <<endl;
    	}
    }
    

4.2 QTimer类

实现一个定时器,提供了更高层次的接口,如信号和槽,还可以设置只运行一次的定时器。QTimer定时器达到定时时间后,就调用槽函数,执行相关操作。

  • 创建定时器:QTimer* timer = new QTimer(this);
  • 开启定时器:timer->start(1000);
  • 停止定时器:timer->stop();
  • connect槽函数:connect(timer, &QTimer::timeout, this, &Widget::updateTimer);
  • 获取当前时间并转换为字符串:QTime::currentTime().toString("格式")
  • 创建只执行一次的定时器:QTimer::singleShot(5000, this, &Widget::close);,此定时器能够与槽函数连接起来
//将当前时间显示在LCD中,每隔间隔一秒闪烁
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    timer = new QTimer(this);//创建QTimer对象,并建立QTimer定时器溢出与槽函数的关系
    connect(timer, &QTimer::timeout, this, &Widget::updateTimer);
    timer->start(1000);//开启定时器
}

void Widget::updateTimer()
{
    QTime time = QTime::currentTime(); //获取当前时间,并转换为字符串
    QString text = time.toString("hh:mm");
    if(time.second()%2 == 0) text[2] = ' ';//更改中间的:,每隔一秒就闪烁一下
    ui->lcdNumber->display(text);//在lcd上展示

    if(++count>= 5)//count记录已过秒数,超过5秒就停止
    {
        timer->stop();
    }   
}
图片名称
//只执行一次的定时器,5秒后关闭窗口
QTimer::singleShot(5000, this, &Widget::close);
图片名称

5 事件过滤器

在刚开始就讨论这这个事件过滤器的主要作用,此处更加详细的记录并使用一下。

事件过滤器实现的是在一个部件中监控其他子部件事件,因此当事件发生触发事件过滤器时,要判断对象以及事件类型。

5.1 事件过滤器的函数形式

  • 先在父部件上安装对子部件的监控:ui->textEdit->installEventFilter(this);
  • 后实现事件过滤器:bool eventFilter(QObject* obj, QEvent*event)

5.2 事件过滤器的实现

步骤:

  1. 判断对象:obj==ui->textEdit
  2. 判断事件:event->type()==QEvent::WheelEvent此处注意的是,事件类型定义在QEvent
  3. 强制类型转换:QWheelEvent* wheelEvent = static_cast<QWheelEvent>(event)
  4. 处理对应的事件并返回bool:若不需要事件继续传递则return true,否则返回false
  5. 父部件对事件处理:如果obj与子部件没有匹配,则返回父部件事件过滤器的处理结果return eventFilter(obj, event)。注意此处实现的事件过滤器是重写了父类的事件过滤器。
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->textEdit->installEventFilter(this);
    ui->spinBox->installEventFilter(this);
}

Widget::~Widget()
{
    delete ui;
}

bool Widget::eventFilter(QObject *obj, QEvent *event)
{
    if(obj == ui->textEdit)//判断是textEdit
    {
        if(event->type() == QEvent::Wheel)//判断事件类型QEvent
        {
            QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);//强制类型转换
            if(wheelEvent->delta()>0) ui->textEdit->zoomIn();
            else ui->textEdit->zoomOut();
            return true;
        }
        else
        {
            return false;
        }
    }
    else if(obj == ui->spinBox)
    {
        if(event->type() == QEvent::KeyRelease)
        {
            QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
            if(keyEvent->key() == Qt::Key_Space)
            {
                ui->spinBox->setValue(0);
            }
            return true;
        }
        else
        {
            return false;
        }
    }
    else return QWidget::eventFilter(obj, event);
}

6 发送事件

发送事件可通过QApplication的静态函数postEvent和sendEvent实现,这两个函数能够将自定义的事件发送给指定部件。

6.1 发送事件函数

static void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
static bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)

两个事件函数都能够实现发送事件,但是区别在于:

  1. sendEvnet发送的事件会立即执行,而postEvent发送的事件会暂时放到队列中。从名字可以看出。
  2. sendEvent的事件不会自动删除,因此需要在栈上创建,而postEvent的事件放在队列中,队列会自动将其删除,因此可以在堆上创建。
  3. postEvent由于暂时放在队列中,因此可以设置优先级

6.2 sendEvent的使用

指定的事件如QEvent::KeyEvent、指定事件的具体动作Qt::Key_Up

  1. 创建按键事件,并设置为Key_Up动作:QKeyEvent keyevent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
  2. 发送事件:QApplication::sendEvent(ui->spinBox, &keyevent);

下面的代码是向spinBox发送按键事件,按键动作为向上长。另外创建一个1s周期的QTimer定时器,定时器溢出时执行槽函数,槽函数中发送事件。最终达到的效果是spinBox数字每秒都会+1.

	Widget::Widget(QWidget *parent)
		: QWidget(parent)
		, ui(new Ui::Widget)
	{
		ui->setupUi(this);
		QTimer* timer = new QTimer(this);
		connect(timer, &QTimer::timeout, this, &Widget::updateSpinBox);
		timer->start(1000);
	}

	Widget::~Widget()
	{
		delete ui;
	}

	void Widget::updateSpinBox()
	{
		QKeyEvent keyevent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
		QApplication::sendEvent(ui->spinBox, &keyevent);
	}
图片名称
posted @ 2022-07-31 14:55  YueLiGo  阅读(494)  评论(0编辑  收藏  举报