1.事件的定义
QT中事件是有专门的类QEvent,常见的有键盘事件QKeyEvent、鼠标事件QMouseEvent和定时器事件QTimerEvent。
例如用鼠标单击下按钮,就会产生一个鼠标事件,按钮会产生一个单击信号。事件可以理解为发出信号的来源,信号的生产者,有了这个鼠标事件产生了这个单击信号。
2.事件和信号的区别
接收者不同
信号的接收者是槽,事件的接收者是一个队列,事件发生时,会产生一个事件对象被插入队列的尾部,系统会循环从队列的头部取事件对象进行处理。
事件处理可以是异步的,信号的处理则是同步的。
单线和多线性
一个信号触发后,可以关联多个槽,多个槽会被执行,当然一个信号也可以触发另外一个信号。而事件则是与窗口相关的,所以事件回调时都是从当前窗口开始,一级一级向上派发,直到有一个窗口返回true,截断了事件的处理为止。
返回值作用
事件处理函数如果返回true,则这个事件处理已完成,QApplication会接着处理下一个事件,而如果返回false,那么事件分派函数会继续向上寻找下一个可以处理该事件的注册方法。信号处理函数的返回值对信号分派器来说是无意义的。
3.事件处理和事件传递
①事件处理
重新实现部件的paintEvent(),mousePressEvent()等事件处理函数。只能用来处理特定部件的特定事件。
重新实现notify()函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。
向QApplication对象上安装事件过滤器。因为一个程序只有一个QApplication对象,所以这样实现的功能与使用notify()函数是相同的,优点是可以同时处理多个事件。
重新实现event()函数。QObject类的event()函数可以在事件到达默认的事件处理函数之前获得该事件。
在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。
- 通过对目标对象调用installEventFilet()来注册监视对象
- 在监视对象的eventFilter()函数中处理目标对象的事件
②事件传递
应用程序产生事件时,事件会先派发给父窗口,由父窗口在内部派发给子窗口,子窗口会进一步传递给子窗口的子窗口,直到遍历到最后没有子窗口的窗口对象,进入子窗口的用户事件处理函数。从子窗口开始,事件会依次进入到父窗口的事件处理函数中,子窗口可以调用event->accept()来中断事件的向上传递。总结来说是先下沉再上浮。
当发现父窗口没有对应的事件触发用户函数时,首先要确认相关功能是否启用,比如鼠标悬停和拖拽功能。
其次需要在子窗口的事件处理函数中,显式调用event->ignore()来使事件继续向上传递。
4.鼠标事件和滚轮事件
①QMouseEvent的详细描述
QMouseEvent 类用来表示一个鼠标事件,当在窗口部件中按下鼠标、释放鼠标和移动鼠标指针时,都会产生鼠标事件 QMouseEvent。
利用 QMouseEvent 类可以获知鼠标是哪个键按下释放了、鼠标指针的当前位置等信息。通常是重定义窗口部件的鼠标事件处理函数来进行一些自定义的操作。
- Qt中的QMouseEvent一般只涉及按下鼠标、释放鼠标和移动鼠标指针等操作,而对鼠标滚轮的响应则通过QWheeEvent来处理。
- 鼠标移动事件只会在按下鼠标按键的情况下才会发生,除非通过显式调用。
- QWidget::setMouseTracking()函数来开启鼠标轨迹,这种情况下只要鼠标指针在移动,就会产生一系列的Qt鼠标事件。
②QMouseEvent的传递
多个重叠的窗口在实现里好比一个递归的倒立树,鼠标事件会沿着鼠标指针所在的父窗口的链表向上传递,直到某个窗口调用accept()函数进行事件处理,否则该事件将被过滤销毁掉。
- 如果想要鼠标指针所在的父窗口不接收该事件,则可以调用函数ignore()予以忽略。
- 如果一个鼠标事件传递给鼠标指针所在的窗口,而该窗口的QT::WA_NoMousePropagation位置为TRUE,则该事件不会通过父窗口继续向上传递。
- 可以使用QWidget::setEnabled()来开启/关闭对应的窗口是否接受键盘和鼠标事件。
③鼠标事件函数
- 鼠标按下事件 mousePressEvent():mousePressEvent() 函数可以区分出鼠标按下时是左键还是右键,以及获取鼠标按下时的坐标值等。
- 鼠标释放事件 mouseReleaseEvent():mousePressEvent() 函数可以区分出鼠标释放时是左键还是右键,以及获取鼠标释放时的坐标值等。
- 鼠标移动事件 mouseMoveEvent():mouseMoveEvent() 函数默认情况下,触发事件需要点击一下,才能触发。可设置为自动触发:setMouseTracking(true)。
QMouseEvent常用成员函数:
- globalPos()、globalX()、globalY()
const QPoint & QMouseEvent::globalPos () const //返回鼠标指针的全局坐标值(类型为QPoint),即相对于PC屏幕的坐标值,而不是相对于当前打开窗口。
int QMouseEvent::globalX () const //返回鼠标事件发生时鼠标指针全局坐标的X值。
int QMouseEvent::globalY () const //返回鼠标事件发生时鼠标指针全局坐标的Y值。
- pos()、posF()、x()、y()
const QPoint & QMouseEvent::pos () const //返回鼠标指针和接受该鼠标事件窗口的相对位置,其中的坐标值为整型。
QPointF QMouseEvent::posF () const //返回鼠标指针在接受该鼠标事件窗口的相对位置,该坐标值用float类型表示可以增加精确度。
int QMouseEvent::x () const //返回鼠标事件发生时,鼠标指针在当前接收鼠标事件的窗口中位置的x坐标值。
int QMouseEvent::y () const //返回鼠标事件发生时,鼠标指针在当前接收鼠标事件的窗口中位置的y坐标值。
④QWheelEvent的详细描述
QWheelEvent 类用来表示鼠标滚轮事件,包含用于描述鼠标滑轮事件的相关参数。
头函数:#include<QWheelEvent>,继承至:QInputEvent。
函数原型:
QWheelEvent::QWheelEvent(const QPoint &pos, int delta, Qt::MouseButtons buttons,Qt::KeyboardModifiers modifiers, Qt::Orientation orient = Qt::Vertical )
功能与参数:
/*** 创建一个wheelEvent对象***/
●参数pos代表鼠标指针在窗口中的当前位置,通常用globalPos()初始化QCursor::pos(),但并不总是正确的。如果需要显示指定一个全局位置,可以用其他的构造函数;
●参数button用于描述在鼠标事件过程中鼠标按键的状态(state);
●delta()可以返回滑动的距离,正数值表示滑轮相对于用户在向前滑动,相反,负数值表示滑轮相对于用户是向后滑动的。;
●参数modifiers用于描述在鼠标事件中鼠标状态位改变时的参数值(比如鼠标的左中右按键的切换);
●参数orient用于指示鼠标滑轮滚动的方向(水平或者垂直)。
5.键盘事件
①focus
一个拥有焦点(focus)的QWidget才可以接受键盘事件。有输入焦点的窗口是活动窗口或活动窗口子窗口或子子窗口等。
焦点移动的方式有以下几种:
●按下Tab或Shift+Tab
注意:文本编译器(一般需要插入Tab),或者WebView(需要Tab来移动超链接焦点) 等
Qt中,需要输入Tab的地方可以用 Ctrl+Tab 或 Ctrl+Shift+Tab 替代。
●点击一个QWidget
建议:只对接受文本输入的Widget启用该功能
●按下键盘的快捷键
QLabel::setBuddy(), QGroupBox,以及 QTabBar 支持
●使用鼠标滚轮
●用户移动焦点
程序将决定被设置focus的Widget的哪一个子Widget获得焦点
注意:如果一个 Widget 已经 grabKeyboard,所有键盘事件将发送到该Widget而不是获得焦点的Widget
②focusPolicy
一个QWidget获得焦点的方式受 focusPolicy 控制
Qt::TabFocus //通过Tab键获得焦点
Qt::ClickFocus //通过被单击获得焦点
Qt::StrongFocus //可通过上面两种方式获得焦点
Qt::NoFocus //不能通过上两种方式获得焦点(默认值),setFocus仍可使其获得焦点
③keypress和keyrelease
首先,我们要是Widget获得焦点,一般设置focusPolicy。
然后要对按键进行响应,我们只需要直接重载:
keyPressEvent
keyReleaseEvent
注意:
对我们不处理的事件,要调用父类的相应事件处理函数。
如果widget当前没有焦点,考虑到事件转发:如果其子widget有焦点,那么该widget未处理的键盘事件将被转发过来。
有时输入焦点不在任何窗口中。这种情况发生在所有程序都是最小化的时候。这时,Windows将继续向活动窗口发送键盘消息,
但是这些消息与发送给非最小化的活动窗口的键盘消息有不同的形式。
④QKeyEvent
在windows下,与键盘事件有关的有8个消息:
- 对产生可显示字符的按键组合,Windows不仅给程序发送按键消息,而且还发送字符消息
- 有些键不产生字符,这些键包括shift键、功能键、光标移动键和特殊字符键如Insert和Delete。对于这些键,Windows只产生按键消息。
这些消息在Qt中只体现在QKeyEvent中。 - 对字符,可通过 QKeyEvent::text() 获得
- 其他键,QKeyEvent::key() 获得一个键值
6.定时器事件和随机数
①定时器事件和随机数
QTimerEvent类用来描述一个定时器事件。对于一个QObject的子类,只需要使用int QObject::startTimer ( int interval)函数来开启一个定时器,这个函数需要输人一个以毫秒为单位的整数作为参数来表明设定的时间,它返回一个整型编号来代表这个定时器。
当定时器溢出时就可以在timerEvent()函数中获取该定时器的编号来进行相关操作。
其实编程中更多的是使用QTimer类来实现一个定时器,它可以使用信号和槽,还可以设置只运行一次的定时器。所以如果使用定时器,那么一般都是使用的QTimer类。
关于随机数,在Qt中是使用qrand()和qsrand()两个函数实现的。
②通过ID使用定时器
使用QTimerEvent的timerId()函数来获取定时器的编号,然后判断是哪一个定时器并分别进行不同的操作。
例:
//在头文件.h中进行声明:
private:
Ui::Widget *ui;
int id1,id2,id3; //定时器的编号
protected:
void timerEvent(QTimerEvent *event); //定时器事件
//在.cpp中进行实现相应的功能:
//构造函数
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
id1 = startTimer(1000); //开启一个1秒定时器,并返回其id
id2 = startTimer(2000);
id3 = startTimer(4000);
}
//定时器事件
void Widget::timerEvent(QTimerEvent *event)
{
//1秒钟时间到,则定时器1溢出
if (event->timerId() == id1)
{
qDebug()<<"timer1";
}
else if(event->timerId() == id2)
{
qDebug()<<"timer2";
}
else if (event->timerId() == id3)
{
qDebug()<<"timer3";
}
}
③通过信号和槽实现定时器
例:
//在头文件.h中进行声明:
private slots:
void timeOut(); //定时器超时函数
//在.cpp中进行实现相应的功能:
//构造函数
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个新的定时器
QTimer *timer = new QTimer(this);
//设置定时器1秒钟超时
timer->start(1000);
//关联定时器的超时信号到槽上
connect(timer,SIGNAL(timeout()),this,SLOT(timeOut()));
}
Widget::~Widget()
{
delete ui;
}
//定时器超时函数
void Widget::timeOut()
{
QTime time = QTime::currentTime(); // 获取当前时间
QString text = time.toString("hh:mm:ss"); // 转换为字符串
if((time.second() % 2) == 0)
{
//每隔一秒就将“:”显示为空格
text[2]=' ';
text[5]=' ';
}
qDebug() << text;
}
//这里在构造函数中开启了一个1秒的定时器,当它溢出时就会发射timeout()信号,这时就会执行我们的定时器溢出处理函数。
//在槽里我们获取了当前的时间,并且将它转换为可以显示的字符串。
④随机数
例:
//在头文件.h中进行声明:
private slots:
void timeOut(); //定时器超时函数
//在.cpp中进行实现相应的功能:
//构造函数
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个新的定时器
QTimer *timer = new QTimer(this);
//设置定时器1秒钟超时
timer->start(1000);
//关联定时器的超时信号到槽上
connect(timer,SIGNAL(timeout()),this,SLOT(timeOut()));
//使用qsrand()函数为随机数设置初值
qsrand(static_cast<uint>( QTime(0, 0,0).secsTo(QTime::currentTime()) ));
}
//定时器超时函数
void Widget::timeOut()
{
int rand = qrand()%300;//产生300以内的正整数
qDebug()<< rand;
}
//在使用qrand()函数产生随机数之前,一般要使用qsrand()函数为其设置初值,如果不设置初值,那么每次运行程序,qrand()都会产生相同的一组随机数。
//为了每次运行程序时,都可以产生不同的随机数,我们要使用qsrand()设置一个不同的初值。这里使用了QTime类的secsTo()函数,
//它表示两个时间点之间所包含的秒数,比如代码中就是指从零点整到当前时间所经过的秒数。
//当使用qrand()要获取一个范围内的数值时,一般是让它与一个整数取余,比如这里与300取余,就会使所有生成的数值在0-299之间。
7.事件过滤器与发送
Qt提供了事件过滤器来实现在一个部件中监控其他多个部件的事件。事件过滤器与其他部件不同,它不是一个类,只是由两个函数组成的一种操作,用来完成一个部件对其他部件的事件的监视。这两个函数分别是 installEventFilter() 和 eventFilter(),都是QObject类中的函数。
事件过滤器介绍:
- 事件过滤器可以对需要的组件接收到的事件进行过滤,以及监控
- 任意的QObject对象都可以作为事件过滤器使用
- 事件过滤器的实现,需要重写eventFilter()函数
- 组件要想被监控,则需要通过installEventFilter()安装事件过滤器
- 事件过滤器能够决定是否将事件转发给组件对象,如图1所示(图1.png)。
(1)与事件过滤器相关的函数
事件过滤器由QObject类中的两个函数来实现。
①installEventFilter,它负责在相应部件上安装事件过滤器,其声明为:
void QObject::installEventFilter(QObject *filterObj);
其中,filterObj参数表示要在其上实现事件过滤器函数的部件。请注意,如果我们在一个部件安装了事件过滤器,一般在其父控件上实现事件过滤器函数。
②eventFilter 函数,我们在此函数中实现事件过滤器。
[virtual] bool QObject::eventFilter(QObject *watched,QEvent *event)
watched:代表被监控的组件 event:代表要转发的事件
返回true,表示该事件也被过滤掉(处理),无需再转发了
返回false,则正常转发给watched
注:
- 该函数在 QObject 类中声明为一个虚函数,因此只能由 QObject 的子类继承使用
- 该函数在 QObject 类中是一个保护成员,因此子类继承时不可以作为一个公有成员
(2)应用程序示例
//.h文件
class MainWindow : public QMainWindow
{
public:
MainWindow();
protected:
bool eventFilter(QObject *obj, QEvent *event);
private:
QTextEdit *textEdit;
};
//.cpp文件
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
在上面的代码中,我们不想让textEdit组件处理键盘按下的事件。所以,首先我们找到这个组件,如果这个事件是键盘事件,则直接返回 true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回 false。对于其它的组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。