Qt开发鼠标事件
引言
个人认为,事件机制是Qt最难以理解且最为精妙的一部分。事件主要分为两种: 在与用户交互时发生 。比如按下鼠标(mousePressEvent),敲击键盘(keyPressEvent)等。 系统自动发生 ,比如计时器事件(timerEvent)等。
在发生事件时(比如说上面说的按下鼠标),就会产生一个QEvent对象(这里是QMouseEvent,为QEvent的子类),这个QEvent对象会传给当前组件的event函数。如果当前组件没有安装 事件过滤器 (这个后面会提到),则会被event函数发放到相应的xxxEvent函数中(这里是mousePressEvent函数)。
需要区分的是: 事件与信号并不相同。
比如:鼠标单击按钮,鼠标事件(QMouseEvent),而按钮本身发射clicked()信号。一般而言我们只需要关注单击信号,不用考虑鼠标事件。但是当我们要对该按钮做额外操作,不想通过信号处理,此时事件就是一个很好的选择。关闭事件(QCloseEvent)是一个常用的事件。 一,事件
Qt 中所有事件类都继承于 QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给 QObject 的 event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(eventhandler)。
信号是通过connect()来绑定槽函数处理响应,那么事件是怎么处理的呢?
处理事件有5种常用的方法: 重新实现部件的paintEvent()、mousePressEvent()等事件处理函数。这是最常用的一种方法,不过只能用来处理特定部件的特定事件 (即需要新建类去实现) 重新实现notify()函数。这个函数的功能强大,提供了完全的控制,可以再事件过滤器得到事件之间就获得他们。但是,它一次只能处理一个事件。 向QApplication对象上安装事件过滤器。因为一个程序只有一个QApplication对象,实现的功能和notify()函数相同,优点是可以同时处理多个事件。 重新实现event()函数。QObject类的event()函数可以在事件达到默认事件处理函数之前获得该事件。 在对象上安装事件过滤器。使用事件过滤器可以再一个界面类中同时处理不同子部件的事件 (在本类中实现)
实际编程中最常用的是方法(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
重写事件框架:
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 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)。当选择全局包含时,就包含了主类。
点击领取Qt学习资料+视频教程~「链接」
其实也有解决的办法:需要在提升界面的头文件处,将工程目录下自定义控件的地址放于此处(本篇地址: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(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组件安装过滤器(调用installEvenFilte r 函数) 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则让其继续被处理。
点击领取Qt学习资料+视频教程~「链接」
实例:通过事件过滤器阻塞上面代码中的鼠标按下事件#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(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(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); }
运行结果:
可以看到在过滤器事件中就监听了鼠标按压(即阻塞了后面的事件分发和鼠标按压)
秋夜女装大佬骑自行车回家,变装白色连衣裙女孩十月的一个深夜,时间已经是十点多了,四周静悄悄的,偌大的写字楼里现在只剩下我一个人了。同事们早就下班回家了,我以加班为借口留到了现在。此时,我早已按捺不住的激动心情,因为我要在这个
湖人听取威少浓眉报价!勇士与库里成被告,杜兰特抱怨篮网队友头条创作挑战赛NBA官方公布了最新一期的新秀排行榜,步行者锋卫摇摆人马瑟林反超受伤缺阵的魔术状元班切罗,首次登上了榜首位置。话说步行者这位置对球员百分百有加成,走了一个同位置的球星
火独赛后!小波特向全队提要求,申京表达谦逊,加鲁巴爆赞防守火箭10192击败独行侠,取胜后小波特表示每场比赛在变好,一开始就在防守端打出价值。值得一提的是,小波特最后时刻的发挥确实主导了局势,他连续得分,帮助球队杀死比赛。当然火箭有没有变
马特邦纳12年职业生涯签了5份合同,为何被称为红曼巴?红曼巴马特邦纳马特邦纳,1980年出生于美国新罕布什尔州,2003年在次轮第16顺位被公牛队选中,但随后被交易至猛龙队,12年职业生涯的后10年全部在马刺队效力,并作为重要的轮换球
葡萄牙VS尼日利亚葡萄牙上届世界杯和西班牙小组同积5分携手出线,淘汰赛首轮不敌乌拉圭止步十六强。本届继续由37岁的C罗领衔,绝对主力迪亚斯坎塞洛N。门德斯B。费尔南德斯上届世界杯都是边缘人物,而现在
夜读散文英雄牌钢笔自打记事起,爸爸就难得回家。每次见到他,眼睛不自觉就被他衬衫口袋上别着的一支黑色钢笔吸引。与钢笔相伴的,还有一枚金灿灿的党徽。为什么一支钢笔会别在爸爸胸前这么多年?我时常想。当我稚
曝卫视跨年晚会阵容湖南台主打流量明星,江苏台专业歌手助阵临近年底活动是越来越多了,最让人期待的跨年晚会也有了最新的动向根据网上的爆料来看,目前已经有四家卫视的阵容在网上曝光了不妨跟着脚步来看看吧!作为一年一次的跨年晚会,可以说是备受期待
进来,给你讲7个超有趣的故事夜读开卷有益你听过原汁原味的中国传统妖怪故事吗?中国妖怪文化源远流长,妖怪数量众多,妖怪故事也尤为精彩。日本著名的妖怪研究学者水木茂称如果要考证日本妖怪的起源,我相信至少有70的原
付雪洁我这辈子最正确的决定,就是嫁给了英雄李文亮说起付雪洁这个名字,可能有一些人并不是很熟悉,但是如果说她是英雄医生李文亮的妻子,相信很多人就特别熟悉了。李文亮医生,我们都知道他是一位好医生,他顾家敬业爱岗处处为别人着想,处处为
象棋陷阱速胜法埋伏奇兵之炮沉底线助攻杀(4)布局提示中炮过河车左边马对屏风马红退巡河车对黑左马盘河1。炮二平五,马进。2。马二进三,卒进。3。车一平二,车平。4。车二进六,马进。5。马八进九,马进。黑跳左马盘河是较好的应法,
9场比赛证明杜锋应该体面离开!姚明篮协太狠,33小时就让他下课北京时间11月17日,男篮大帅杜锋的执教经历到底是成功还是失败,对于这一点很多人都在质疑,甚至有不少人都说杜锋这次下课是自作自受。确实姚明篮协的这次动作显得雷厉风行,但是在这种背景