Qt 系统事件
1. Qt中的事件
- 事件是对各种引用程序需要知道的由应用程序内部或者外部产生的事情或动作的统称。
- Qt中使用一个对象来表示一个时间,继承自QEvent类。
- 事件与信号并不相同,它们是两个不同层面的东西,发出者不同,作用也不同。
- 比如单击一下界面上的按钮,那么就会产生鼠标事件 QMouseEvent ,这个事件并不是按钮产生的,而按钮被按下,所以按钮会发射 clicked() 单击信号,这个信号是按钮产生的。
1.1 事件的处理
- 一个事件由一个特定的QEvent子类来表示。
- 有时一个事件由包含多个事件类型,比如鼠标事件可以分为鼠标按下、双击和移动等操作。这些事件类型都由QEvent的枚举型 QEvent::Type 来表示。
- 虽然 QEvent 的子类可以表示一个事件,但是却不能用来处理事件,以下是 QCoreApplication 类的 notify() 函数的帮助文档给出的5中处理事件的方法:
- 方法一:重新实现部件的 paintEvent()、mousePressEvent() 等事件处理函数。这是最常用的一种方法,不过只能用来处理特定部件的特定事件,例如实现拖放操作。
- 方法二:重新实现 notify() 函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是它一次只能处理一个事件。
- 方法三:向QApplication对象上安装事件过滤器。因为一个程序只有一个 QApplication 对象,所以这样实现的功能与使用 notify() 函数是相同的,优点是可以同时处理多个事件。
- 方法四:重新实现event() 函数。QObject类的event() 函数可以在事件到达默认的时间处理函数之前获得该事件。
- 方法五:在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。
- 在实际编程中,最常用的是方法以2,其次是方法五。因为方法二需要继承自QApplication类;而方法三要使用一个全局的事件过滤器,这将减缓事件的传递,所以这两种方法虽然功能强大,但却很少用到。
1.2 事件的传递
- 每个程序main() 函数的最后都会调用QApplication类的exec() 函数,它会使Qt应用程序进入事件循环,这样就可以使应用程序在运行时接收发生的各种事件。
- 一旦有事件发生,Qt便会构建一个相应的QEvent子类的对象来表示它,然后将它传递给相应的QObject对象或其子对象。
1.2.1 程序练习
- 新建一个Qt Widgets 应用项目,基类选择QWidget.
- 添加新类文件 MyLineEdit,基类为 QLineEdit
- 编辑 mylineedit.h
#ifndef MYLINEEDIT_H
#define MYLINEEDIT_H
#include <QLineEdit> //包含头文件
class MyLineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit MyLineEdit(QWidget * parent = 0);
protected:
void keyPressEvent(QKeyEvent * event); //按键按下事件声明
};
#endif // MYLINEEDIT_H
- 编辑mylineedit.cpp
#include "mylineedit.h"
#include <QKeyEvent>
#include <QDebug>
MyLineEdit::MyLineEdit(QWidget *parent):QLineEdit(parent)
{
}
void MyLineEdit::keyPressEvent(QKeyEvent *event) //键盘按下事件
{
qDebug()<<tr("MyLineEdit键盘按下事件");
QLineEdit::keyPressEvent(event); //执行QLineEdit类的默认事件处理
event->ignore(); //忽略该事件
}
- 编辑widget.h文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class MyLineEdit;
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
MyLineEdit *lineEdit;
protected:
void keyPressEvent(QKeyEvent * event);
bool eventFilter(QObject *obj, QEvent *event);
};
#endif // WIDGET_H
- 编辑widget.cpp文件
#include "widget.h"
#include "ui_widget.h"
#include "mylineedit.h"
#include <QKeyEvent>
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
lineEdit=new MyLineEdit(this);
lineEdit->move(100,100);
lineEdit->installEventFilter(this); //在Widget上为lineEdit安装事件过滤器
}
Widget::~Widget()
{
delete ui;
}
void Widget::keyPressEvent(QKeyEvent *event)
{
qDebug()<<tr("Widget键盘按下事件");
}
bool Widget::eventFilter(QObject *obj, QEvent *event) //事件过滤器
{
if(obj == lineEdit)
{
if(event->type()==QEvent::KeyPress)
qDebug()<<tr("Widget的事件过滤器");
}
return QWidget::eventFilter(obj,event);
}
- 上面写了这么一堆,目的就是为了看一看事件的传递顺序:
- 先是事件过滤器,然后是焦点部件的 event() 函数,最后是焦点部件的事件处理函数。如果焦点部件忽略了该事件,那么会执行父部件的事件处理函数。
2. 鼠标和滚轮事件
- QMouseEvent 类用来表示一个鼠标事件,利用QMouseEvent 类可以获知哪个键被按下、鼠标指针的当前位置等信息。
- QWheelEvent 类用来表示鼠标滚轮事件,主要用来获取滚轮移动的方向和距离。
2.1 程序练习代码
- 例子要实现的效果:
- 可以在界面上按着鼠标左键来拖动窗口;
- 双击鼠标左键来使其全屏;
- 按着鼠标右键使指针变为一个自定义的图片;
- 使用滚轮缩放编辑器中的内容。
- 编辑widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
QPoint offset; //用来存储鼠标指针位置与窗口位置的差值
protected:
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
};
#endif // WIDGET_H
- 编辑widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMouseEvent>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
QCursor cursor; //创建光标对象
cursor.setShape(Qt::OpenHandCursor); //设置光标形状(小手形状的光标--松开的手)
setCursor(cursor); //使用光标
//如果想不安鼠标按键,也可以获取鼠标移动事件,就添加下面这行代码
setMouseTracking(true); //设置鼠标跟踪
}
Widget::~Widget()
{
delete ui;
}
//鼠标按下事件
void Widget::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton)
{
QCursor cursor;
cursor.setShape((Qt::ClosedHandCursor));
QApplication::setOverrideCursor(cursor); //使用鼠标指针暂时改变形状(攥紧的小手)
offset=event->globalPos()-pos(); //获取指针位置和窗口位置的差值
}
else if(event->button()==Qt::RightButton)
{
QCursor cursor(QPixmap("../mymouseevent/logo.png"));
QApplication::setOverrideCursor(cursor); //使用自定义的图片作为鼠标指针
}
}
//鼠标释放事件
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event) //这样编译时就不会出现警告了
QApplication::restoreOverrideCursor(); //恢复鼠标指针形状
}
//鼠标双击事件
void Widget::mouseDoubleClickEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton)
{
if(windowState()!=Qt::WindowFullScreen)
setWindowState(Qt::WindowFullScreen); //全屏
else
setWindowState(Qt::WindowNoState); //恢复以前的大小
}
}
//鼠标移动事件
void Widget::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons()&Qt::LeftButton) //这里必须使用buttons
{
QPoint temp;
temp=event->globalPos()-offset;
move(temp); //使用鼠标指针当前的位置减去差值,就得到了窗口应该移动的位置
}
}
//滚轮事件
void Widget::wheelEvent(QWheelEvent *event)
{
if(event->delta()>0) //当滚轮远离使用者时
{
ui->textEdit->zoomIn(); //放大
}
else //当滚轮走向使用者时
{
ui->textEdit->zoomOut();//缩小
}
//每当滚轮滚动一下,默认是15°,delta()函数会返回15*18即120.
}
3. 键盘事件
- QKeyEvent类用来描述一个键盘事件。当键盘按键被按下或者被释放时,键盘事件就会发送给拥有键盘输入焦点的部件。QKeyEvent的 key() 函数可以获取具体的按键。
- 需要特别说明的是:回车按键是Qt::Key::Return,键盘上的一些修饰键,比如Ctrl 和 Shift 等,需要使用 QEvent 的 modifiers() 函数来获取。
3.1 程序练习代码
- 例程实现以下效果:
- 按下向上方向按键按钮上移;
- 按下向左方向按键按钮左移;
- 同时按下向上方向按键 和 向左方向按键 按钮斜着向左上移动;
- 同时按下Ctrl 和 M, 窗口最大化,松开之后,窗口恢复原来大小
- 编辑widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
bool keyUp; //向上方向键按下的标志
bool keyLeft; //向左方向键按下的标志
bool move; //是否完成了一次移动
protected:
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
};
#endif // WIDGET_H
- 编辑widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QKeyEvent>
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
setFocus(); //这一句千万别落下
keyUp = false;
keyLeft = false;
move = false;
}
Widget::~Widget()
{
delete ui;
}
//键盘按下事件
void Widget::keyPressEvent(QKeyEvent *event)
{
if(event->modifiers()==Qt::ControlModifier) //是否按下Ctrl键
{
if(event->key()==Qt::Key_M) //是否按下M键
setWindowState(Qt::WindowMaximized); //窗口最大化
}
else if(event->key() == Qt::Key_Up){ // 如果是向上方向键
qDebug() << "press:"<<event->isAutoRepeat(); // 是否自动重复
}
if(event->key()==Qt::Key_Up)
{
if(event->isAutoRepeat())
return; //按键重复时不做处理
keyUp = true;
}
if (event->key() == Qt::Key_Up)
{
if(event->isAutoRepeat())
return; // 按键重复时不做处理
}
else if(event->key()==Qt::Key_Left)
{
if(event->isAutoRepeat())
return; //按键重复时不做处理
keyLeft = true;
}
else
{
QWidget::keyPressEvent(event);
}
}
//键盘释放事件
void Widget::keyReleaseEvent(QKeyEvent *event)
{
if(event->modifiers()==Qt::ControlModifier) //是否按下Ctrl键
{
if(event->key()==Qt::Key_M) //是否按下M键
setWindowState(Qt::WindowNoState); //窗口恢复原来大小
}
else if(event->key()==Qt::Key_Up)
{
if(event->isAutoRepeat())
return;
keyUp = false; //释放按键后将标志设置为false
if(move) //如果已经完成了移动
{
move = false; //设置标志位false
return; //直接返回
}
if(keyLeft) //如果向左方向键已经按下且没有释放
{
ui->pushButton->move(30,80); //斜移
qDebug()<<"斜移";
move = true;
}
else
{
ui->pushButton->move(120,80); //上移
qDebug()<<"上移";
}
}
else if(event->key()==Qt::Key_Left)
{
if(event->isAutoRepeat()) return;
keyLeft = false;
if(move)
{
move=false;
return;
}
if(keyUp)
{
ui->pushButton->move(30,80);
move = true;
}
else
{
ui->pushButton->move(30,120);
qDebug()<<"左移";
}
}
else if(event->key()==Qt::Key_Down)
{
ui->pushButton->move(120,120); //使用向下方向键还原按钮的位置
}
else
{
QWidget::keyReleaseEvent(event);
}
}
4. 定时器事件和随机数
- QTimerEvent 类用来描述一个定时器事件。对于一个QObject的子类,只需要使用 int QObject::startTimer(int interval) 函数就可以开启一个定时器,这个函数需要输入一个以毫秒为单位的整数作为参数来表明设定时间,函数返回一个整型编号来代表这个定时器。当定时器溢出时,可以在 timerEvent() 函数中进行操作。
- 其实编程中更多的是使用QTimer类来实现一个定时器,它提供了更高层次的编程接口,比如可以使用信号和槽,还可以设置只运行一次的定时器。
- 关于随机数,Qt中是使用qrand() 和 qsrand() 两个函数实现的。
4.1 程序练习代码
- 下面例程完成以下功能:
- 在界面上显示时间;
- 每隔1s,时间中的“:”闪烁一次;
- 时间这个控件在对话框中随机移动。
- 5s后程序自动关闭
- 编辑widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
int id1,id2,id3;
private slots:
void timerUpdate();
protected:
void timerEvent(QTimerEvent *event);
};
#endif // WIDGET_H
- 编辑widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QTimer>
#include <QTime>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
id1 = startTimer(1000); //开启一个定时1s的定时器,返回ID
id2 = startTimer(1500); //startTimer()函数参数是以毫秒为单位的
id3 = startTimer(2200);
QTimer * timer = new QTimer(this);
connect(timer,&QTimer::timeout,this,&Widget::timerUpdate);
timer->start(1000);
//随机数
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
//singleShot()函数开启一个只运行一次的定时器,下面代码让程序运行5s后自动关闭
QTimer::singleShot(5000,this,&Widget::close);
}
Widget::~Widget()
{
delete ui;
}
//定时器溢出处理
void Widget::timerUpdate()
{
QTime time = QTime::currentTime(); //获取当前时间
QString text = time.toString("hh:mm"); //转化为字符
if((time.second()%2)==0) //每个1s, ":"会显示为空格,从而形成闪烁的效果
text[2]=' ';
ui->lcdNumber->display(text);
int rand = qrand()%300; //产生300以内的正整数
ui->lcdNumber->move(rand,rand);
}
void Widget::timerEvent(QTimerEvent *event)
{
if(event->timerId()==id1)
{
qDebug()<<"timer1";
}
else if(event->timerId()==id2)
{
qDebug()<<"timer2";
}
else if(event->timerId()==id3)
{
qDebug()<<"timer3";
}
}
5. 事件过滤器与事件的发送
- Qt中提供了事件过滤器来实现在一个部件中监控其他多个部件的事件。
- 事件过滤器与其他部件不同,它不是一个类,只是有两个函数组成的一种操作,用来完成一个部件对其他部件的监视。
- 这两个函数分别是installEventFilter() 和 eventFilter() 都是QObject类中的函数。
5.1 程序练习代码
-
下面例程完成以下功能:
- 滚轮调整字体大小;
- 滚轮调整控件中的数字变大变小;
-
编辑widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
protected:
bool eventFilter(QObject * obj,QEvent * event);
};
#endif // WIDGET_H
- 编辑widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QKeyEvent>
#include <QWheelEvent>
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)
{
if(event->type()==QEvent::Wheel)
{
//将event强制转换为发生的事件的类型
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::KeyPress)
{
QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key()==Qt::Key_Space)
{
ui->spinBox->setValue(0);
return true;
}
else
return false;
}
else
return false;
}
else
return QWidget::eventFilter(obj,event);
//如果对一个事件进行处理,而且不希望它在后面的传递过程中再被处理,那么就返回true,否则返回false
}
- 使用事件过滤器可以很容易地处理多个部件的多个事件。
- 如果选择分别子类化各个部件,然后重新实现它们对应的各个事件处理函数,那样就很麻烦了。