Qt开发-鼠标事件

2023-05-16

引言

个人认为,事件机制是Qt最难以理解且最为精妙的一部分。事件主要分为两种:

  1. 在与用户交互时发生。比如按下鼠标(mousePressEvent),敲击键盘(keyPressEvent)等。

  1. 系统自动发生,比如计时器事件(timerEvent)等。

在发生事件时(比如说上面说的按下鼠标),就会产生一个QEvent对象(这里是QMouseEvent,为QEvent的子类),这个QEvent对象会传给当前组件的event函数。如果当前组件没有安装事件过滤器(这个后面会提到),则会被event函数发放到相应的xxxEvent函数中(这里是mousePressEvent函数)。

需要区分的是:事件与信号并不相同。

比如:鼠标单击按钮,鼠标事件(QMouseEvent),而按钮本身发射clicked()信号。一般而言我们只需要关注单击信号,不用考虑鼠标事件。但是当我们要对该按钮做额外操作,不想通过信号处理,此时事件就是一个很好的选择。关闭事件(QCloseEvent)是一个常用的事件。


一,事件

Qt 中所有事件类都继承于 QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给 QObject 的 event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(eventhandler)。

信号是通过connect()来绑定槽函数处理响应,那么事件是怎么处理的呢?

处理事件有5种常用的方法:

  1. 重新实现部件的paintEvent()、mousePressEvent()等事件处理函数。这是最常用的一种方法,不过只能用来处理特定部件的特定事件(即需要新建类去实现)

  1. 重新实现notify()函数。这个函数的功能强大,提供了完全的控制,可以再事件过滤器得到事件之间就获得他们。但是,它一次只能处理一个事件。

  1. 向QApplication对象上安装事件过滤器。因为一个程序只有一个QApplication对象,实现的功能和notify()函数相同,优点是可以同时处理多个事件。

  1. 重新实现event()函数。QObject类的event()函数可以在事件达到默认事件处理函数之前获得该事件。

  1. 在对象上安装事件过滤器。使用事件过滤器可以再一个界面类中同时处理不同子部件的事件(在本类中实现)

实际编程中最常用的是方法(1),其次是方法(5)。方法2要继承QApplication类,方法3需要全局的事件过滤器,减缓事件的传递。

鼠标事件:

常用的鼠标事件:(本篇处理事件用的是方法一:重写鼠标事件)

  • void mousePressEvent(QMouseEvent *event); //单击

  • void mouseReleaseEvent(QMouseEvent *event); //释放

  • void mouseDoubleClickEvent(QMouseEvent *event); //双击

  • void mouseMoveEvent(QMouseEvent *event); //移动

  • void wheelEvent(QWheelEvent *event); //滑轮

鼠标事件使用的时候,加头文件: #include <QMouseEvent>

重写事件框架:

1️⃣鼠标按下事件

void Widget::mousePressEvent(QMouseEvent *event)
{
    // 如果是鼠标左键按下   
    if(event->button() == Qt::LeftButton){
        ···
    }
    // 如果是鼠标右键按下
    else if(event->button() == Qt::RightButton){
       ···
    }
}

2️⃣鼠标移动事件

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    // 这里必须使用buttons()
    if(event->buttons() & Qt::LeftButton){  //进行的按位与
       ···
    }
}

默认情况下,触发事件需要点击一下,才能触发。可设置为自动触发:setMouseTracking(true);

3️⃣鼠标释放事件

void Widget::mouseReleaseEvent(QMouseEvent *event)
{
   ···
}

4️⃣鼠标双击事件

void Widget::mouseDoubleClickEvent(QMouseEvent *event)
{
    // 如果是鼠标左键按下
    if(event->button() == Qt::LeftButton){
      
        ···
    }
}

5️⃣滚轮事件

void Widget::wheelEvent(QWheelEvent *event)
{
    // 当滚轮远离使用者时
    if(event->delta() > 0){
        ···
    }else{//当滚轮向使用者方向旋转时
        ···
    }
}

实例演示(在label控件中,移动鼠标获取实时位置,并显示在界面上)

  • 创建mylabel类,基类设置为QLabel

这里用了类似自定义控件的方法,对Mylabel类进行封装。设置基类QLabel 是为了在ui界面中提升label控件(即将label控件和Mylabel关联,提升时候必须二者基类相同)

  • 在mylabel.h中声明鼠标事件

#pragma once
#include <qlabel.h>

class mylabel : public QLabel
{
public:
    mylabel(QWidget* parent = 0);
    ~mylabel();
public:
    //鼠标移动事件
    void mouseMoveEvent(QMouseEvent* event);
    //鼠标按下事件
    void mousePressEvent(QMouseEvent* event);
    //鼠标释放事件
    void mouseReleaseEvent(QMouseEvent* event);
};
  • 在mylabel.cpp中重写事件

#include "mylabel.h"
#include"QMouseEvent"


mylabel::mylabel(QWidget* parent) :QLabel(parent)
{
    
}
mylabel::~mylabel()
{

}
//鼠标移动显示坐标
void mylabel::mouseMoveEvent(QMouseEvent* event)
{
    if (event->buttons() & Qt::LeftButton)  //进行的按位与(只有左键点击移动才满足)
    { 
        QString str = QString("Move:(X:%1,Y:%2)").arg(event->x()).arg(event->y());
         this->setText(str);
         
    }
    
}
//鼠标按下显示“ok,mouse is press”
void mylabel::mousePressEvent(QMouseEvent* event)
{
    setText("Ok, mouse is press");

}
//鼠标释放清除显示
void mylabel::mouseReleaseEvent(QMouseEvent* event)
{
    setText(" ");
}
  • 在主函数(QTest.cpp)中声明mylabel的类对象(即声明一个mylabel类的label控件)

#include "qtest.h"

QTest::QTest(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    //声明mylabel类的控件
    mylabel* label1 = new mylabel(this);
    label1->setGeometry(QRect(130, 100, 271, 161));
    //设置边框
    label1->setFrameShape(QFrame::Panel);
}

另外,当调用setMouseTracking(true);时(即设置鼠标状态为自动触发),需要将鼠标移动事件的if语句去掉(因为不需要点击触发了)

修改maylabel.cpp事件:

#include "mylabel.h"
#include"QMouseEvent"


mylabel::mylabel(QWidget* parent) :QLabel(parent)
{
    //设置鼠标状态(自动触发)
    setMouseTracking(true);
}
mylabel::~mylabel()
{

}
//鼠标移动显示坐标
void mylabel::mouseMoveEvent(QMouseEvent* event)
{
   QString str = QString("Move:(X:%1,Y:%2)").arg(event->x()).arg(event->y());
   this->setText(str);
}
//鼠标按下显示“ok,mouse is press”
void mylabel::mousePressEvent(QMouseEvent* event)
{
    setText("Ok, mouse is press");

}
//鼠标释放清除显示
void mylabel::mouseReleaseEvent(QMouseEvent* event)
{
    setText(" ");
}

效果展示:


😒这里用的是代码创建label控件,那么能不能用ui界面编辑然后在对label控件提升呢?

答案是可以的,但是需要注意的是:此处不能选择全局包含

否则会出现:

我想其中的原因主要是因为:

本实例是新建了一个mylabel类,而不是像QT常用控件(三)——自定义控件封装 - 唯有自己强大 - 博客园 (cnblogs.com)这篇博文中直接新添加了一个设计师界面类(即包含ui .h .cpp)。当选择全局包含时,就包含了主类。

其实也有解决的办法:需要在提升界面的头文件处,将工程目录下自定义控件的地址放于此处(本篇地址:C:/Users/WFD/Desktop/QTest/QTest/mylabel.h)


二,事件的分发:event函数

上面提到的xxxEvent函数,称为事件处理器(event handler)。而event函数的作用就在于事件的分发。如果想在事件的分发之前就进行一些操作,比如监听(阻塞)鼠标按下事件。

如果希望在事件分发之前做一些操作,就可以重写这个 event()函数了。比如我们希望阻塞鼠标按下事件,那么我们就在新建的Mylabel类中重写event()函数(该类的父类是QLabel)

  • 在Mylabel.h中声明event事件

#include"qlabel.h"
class Mylabel : public QLabel
{
public:
    explicit Mylabel(QWidget* parent = 0);

    //鼠标按下事件
    void mousePressEvent(QMouseEvent* event); 
    //鼠标释放事件
    void mouseReleaseEvent(QMouseEvent* event);
    //声明event事件
    bool event(QEvent* e);
};
  • 在Mylabel.cpp中重写event事件。

#include "Mylabel.h"
#include"QMouseEvent"

Mylabel::Mylabel(QWidget* parent) :QLabel(parent)
{

}

//重写鼠标按下事件
void Mylabel::mousePressEvent(QMouseEvent* event)
{
    this->setText(QString("mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
}
//重写鼠标释放事件
void Mylabel::mouseReleaseEvent(QMouseEvent* event)
{
    this->setText("mouse is release ");
}
//重写event事件
bool Mylabel::event(QEvent* e)
{
    //如果鼠标按下,再事件分发中做拦截
    if (e->type()==QEvent::MouseButtonPress)
    {
        //静态转换(将QEvent的对象转换为QMouseEvent对象)
        QMouseEvent* event = static_cast<QMouseEvent*>(e);
        this->setText(QString("event mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
        return true;//返回ture,说明用户自己处理事件,不往下分发(即拦截上面的按下事件)
    }
    return QLabel::event(e);
}

点击鼠标可以看到,触发的是event的事件(即阻塞了mousePressEvent的事件)。特别需要注意的是:在将不需要阻塞分发的时候,需要分发给父类的event函数处理。即(return QLable::event(e);)

由此可以见,event()是一个集中处理不同类型的事件的地方。如果你不想重写一大堆事件处理器,就可以重写这个 event()函数,通过 QEvent::type()判断不同的事件。鉴于重写 event()函数需要十分小心注意父类的同名函数的调用,一不留神就可能出现问题,所以一般还是建议只重写事件处理器(当然,也必须记得是不是应该调用父类的同名处理器)。

三,事件过滤器(Even Filter)

某些应用场景下,需要拦截某个组件发生的事件,让这个事件不再向其他组件进行传播,这时候可以为这个组件或其父组件安装一个事件过滤器,该过滤器在event分发之前进行拦截。

事件的过滤有两个步骤:

1️⃣对QObject组件安装过滤器(调用installEvenFilter函数)

void QObject::installEventFilter ( QObject * filterObj );

参数filterobj 是指谁为组件安装过滤器(一般是父类)

  • 这个函数接受一个 QObject *类型的参数。记得刚刚我们说的,eventFilter()函数是 QObject 的一个成员函数,因此,任意 QObject 都可以作为事件过滤器(问题在于,如果你没有重写 eventFilter()函数,这个事件过滤器是没有任何作用的,因为默认什么都不会过滤)。已经存在的过滤器则可以通过QObject::removeEventFilter()函数移除。

  • 我们可以向一个对象上面安装多个事件处理器 ,只要调用多次installEventFilter()函数。如果一个对象存在多个事件过滤器,那么,最后一个安装的会第一个执行,也就是后进先执行的顺序。

2️⃣事件过滤器的重写(evenFilter函数)

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

可以看到,函数有两个参数,一个为具体发生事件的组件,一个为发生的事件(产生的QEvent对象)。当事件是我们感兴趣的类型,可以就地进行处理,并令其不再转发给其他组件。函数的返回值也是bool类型,作用跟even函数类似,返回true为不再转发,false则让其继续被处理。

实例:通过事件过滤器阻塞上面代码中的鼠标按下事件

#include "qtest.h"
#include"qmouseevent"

QTest::QTest(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    //第一步:给label添加过滤器
    ui.label->installEventFilter(this);


}
//第二步:重写过滤事件
bool QTest::eventFilter(QObject* obj, QEvent* e)
{
    if (obj == ui.label)
    {
        //如果鼠标按下,再事件分发中做拦截
        if (e->type() == QEvent::MouseButtonPress)
        {
            QMouseEvent* event = static_cast<QMouseEvent*>(e);
            ui.label->setText(QString("eventfilter mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
            return true;//返回ture,说明用户自己处理事件,不往下分发(即拦截上面的按下事件)
        }
    }
    return QWidget::eventFilter(obj, e);
}

//重写鼠标按下事件
void QTest::mousePressEvent(QMouseEvent* event)
{
    ui.label->setText(QString("mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
}

//重写事件分发
bool QTest::event(QEvent* e)
{
    //如果鼠标按下,再事件分发中做拦截
    if (e->type() == QEvent::MouseButtonPress)
    {
        QMouseEvent* event = static_cast<QMouseEvent*>(e);
        ui.label->setText(QString("event mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
        return true;//返回ture,说明用户自己处理事件,不往下分发(即拦截上面的按下事件)
    }
    return QWidget::event(e);
}

运行结果:

可以看到在过滤器事件中就监听了鼠标按压(即阻塞了后面的事件分发和鼠标按压)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt开发-鼠标事件 的相关文章

随机推荐

  • 基于Redis的MessageQueue队列封装

    原创文章 xff0c 转载请注明出处 xff1a http www huyanping cn p 61 275 作者 xff1a Jenner Redis的链表List可以用来做链表 xff0c 高并发的特性非常适合做分布式的并行消息传递
  • 基于PHP的crontab定时任务管理

    BY JENNER 2014年11月10日 阅读次数 xff1a 6 linux的crontab一直是服务器运维 业务开展的利器 但当定时任务增多时 xff0c 管理和迁移都变得很麻烦 xff0c 而且容易出问题 下面提供了一个使用php编
  • PHP模拟SQL的GROUP BY算法

    BY JENNER 2015年1月24日 阅读次数 xff1a 25 github地址 xff1a https github com huyanping Zebra PHP ArrayGroupBy packagist地址 xff1a ht
  • flume:支持重命名、移动文件的roll file sink升级版

    原创文章 xff0c 转载请注明 xff1a 转载自始终不够 本文链接地址 flume xff1a 支持重命名 移动文件的roll file sink升级版 转载请注明 xff1a 始终不够 flume xff1a 支持重命名 移动文件的r
  • wordpress全栈优化

    原创文章 xff0c 转载请注明 xff1a 转载自始终不够 本文链接地址 wordpress全栈优化 转载请注明 xff1a 始终不够 wordpress全栈优化 从最开始计算 xff0c 始终不够 个人博客上线已经有两年多了 从最开始就
  • 递归与循环

    原创文章 xff0c 转载请注明 xff1a 转载自始终不够 本文链接地址 递归与循环 转载请注明 xff1a 始终不够 递归与循环 大一学C 43 43 的时候 xff0c 老师说过递归与循环是可以相互转化的 xff0c 当时好像是用来两
  • 安卓仿知乎个人主页,实现嵌套滑动和渐隐效果

    本篇文章已经授权微信公共号guolin blog 郭霖 独家发布 转载请注明作者AndroidMsky和出处 http blog csdn net AndroidMsky article details 53784984 本文github
  • PHP异步编程简述

    概述 异步编程 xff0c 我们从字面上理解 xff0c 可以理解为代码非同步执行的 异步编程可以归结为四种模式 xff1a 回调 事件监听 发布 订阅 promise模式 我们最熟悉的两种模式是回调和事件监听 xff0c 举两个最简单的j
  • CSV是什么文件格式

    CSV 即 Comma Separate Values xff0c 这种文件格式经常用来作为不同程序之间的数据交互的格式 具体文件格式 每条记录占一行 以逗号为分隔符 逗号前后的空格会被忽略 字段中包含有逗号 xff0c 该字段必须用双引号
  • 写给4年前开始编程序的自己

    最近在网上看到有人写了一篇关于 写给4年前没有开始做设计的自己 xff0c 突然也想写这样一篇文章 具体那篇文章的内容我并没有细读 xff0c 防止自己的思路照着他的来 首先 xff0c 我先简单介绍下自己 xff0c 好为后面的内容做一个
  • STM32单片机(七). USART串口、IIC和CAN通信

    在简单的学习过了STM32中的简单外设以及中断系统后 xff0c 在本章节中开始介绍STM32芯片中各个通信接口的配置 在计算机中 xff0c 按数据传输方式可分为串行通信以及并行通信 xff1b 按数据同步方式可分为异步通信和同步通信 x
  • VINS-Mono论文笔记(中)

    VINS Mono论文笔记 中 前言1 初始化过程1 1 视觉重构1 2 视觉惯性联合 2 紧耦合的单目VIO系统2 1 公式2 2 imu残差2 3 视觉残差2 4 边缘化残差2 5 针对相机实时帧率的纯运动视觉惯性状态估计器2 6 im
  • Qt生成exe文件并成功运行

    Qt程序写完后 xff0c 想要生成一个exe文件 xff0c 那么可以参考以下方法 工具 xff1a Qt5 9 9 我们以程序2048为例 将左下角debug改为release xff0c 然后点击左侧 项目 xff0c 找到build
  • 【Qt入门第二篇】基础(二)编写Qt多窗口程序

    导语 程序要实现的功能是 xff1a 程序开始出现一个对话框 xff0c 按下按钮后便能进入主窗口 xff0c 如果直接关闭这个对话框 xff0c 便不能进入主窗口 xff0c 整个程序也将退出 当进入主窗口后 xff0c 我们按下按钮 x
  • Qt之统一的UI界面格式基调,漂亮的UI界面

    今天主要谈谈Qt UI界面统一样式 格式基调 的问题 xff1b 例如在window系统上 xff0c 几乎所有的窗口都有标题栏和状态栏以及中央部件 xff0c 而且每一个标题栏和状态栏以及中央部件样式都保持一致的 xff1b 但是在实际开
  • qt plaintextedit使用_qt获取lineedit的内容

    QLineEdit和QTextEdit都是文本框类 xff0c QLineEdit类是单行文本框控件 xff0c 可以输入单行字符串 QTextEdit类是多行文本框控件 xff0c 可以显示多行文本内容 xff0c 当文本内容超出控件显示
  • Qt实现表格控件

    一 概述 最近在研究QTableView支持多级表头的事情 xff0c 百度了下网上资料还是挺多的 实现的方式总的来说有2种 xff0c 效果都还不错 xff0c 最主要是搞懂其中的原理 xff0c 做到以不变应万变 实现多级表头的方式有以
  • 从h5调起原生APP到自己调起知乎页面

    转载请注明作者AndroidMsky和出处 xff1a http blog csdn net AndroidMsky article details 54316327 效果 xff1a 这篇算兴趣加技术篇 xff0c 和之前的抢红包博文和接
  • Qt经验-按钮长按事件分析

    引言 最近在做qt项目 xff0c 需要对button按钮添加一个长按事件 xff08 比如点击按钮 xff0c 开始运动 松开按钮 xff0c 运动停止 xff09 查了些许资料 xff0c xff08 差点误把QPushButton的p
  • Qt开发-鼠标事件

    引言 个人认为 xff0c 事件机制是Qt最难以理解且最为精妙的一部分 事件主要分为两种 xff1a 在与用户交互时发生 比如按下鼠标 xff08 mousePressEvent xff09 xff0c 敲击键盘 xff08 keyPres