赞
踩
事件event是由系统或者Qt本身在不同的时刻发出的,主要包括用户事件与定时器事件,
当用户按下鼠标、敲下键盘或者是窗口需要重新绘制的时候,都会发出一个相应的事件,一些事件是在对用户操作做出响应时进行发出(如键盘事件等);另一些事件则是由系统自动发出(如计时器事件)。
exec()
函数之后,程序将进入事件循环来监听应用程序的事件。在所有组件的父类QWidget中,定义了很多事件处理的回调函数,如
鼠标双击、鼠标按下、鼠标释放、鼠标移动、鼠标轨迹追踪,
enterEvent进入控件Widget事件,
以上这些函数都是protected virtual的,可以在子类中重新实现,重写的鼠标事件的主要步骤如下:
如果想要对控件进行构造、析构、事件捕捉等自定义操作,需要利用自定义控件来实现。这里省略对控件ui界面的设计,直接对widget控件的功能进行自定义操作,操作如下:
创建QLabel的子类MyLabel类,右键项目添加新文件,选择C++中的C++class不带有ui界面,如下图所示:
设置好需要继承的父类以及,根据语义确定需要自定义的类名称(这里起名为MyLabel)
在MyLabel中重写鼠标的各种事件,包括
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);
//重写虚函数
virtual void mousePressEvent(QMouseEvent *ev);
virtual void mouseReleaseEvent(QMouseEvent *ev);
virtual void mouseMoveEvent(QMouseEvent *ev);
具体代码如下所示:
#ifndef MYLABEL_H #define MYLABEL_H #include <QMainWindow> #include <QLabel> class MyLabel : public QLabel { Q_OBJECT public: explicit MyLabel(QWidget *parent = nullptr); void enterEvent(QEvent *event); void leaveEvent(QEvent *event); //重写虚函数 virtual void mousePressEvent(QMouseEvent *ev); virtual void mouseReleaseEvent(QMouseEvent *ev); virtual void mouseMoveEvent(QMouseEvent *ev); signals: public slots: }; #endif // MYLABEL_H
void MyLabel::enterEvent(QEvent *event) { qDebug() << "mouse entered."; } void MyLabel::leaveEvent(QEvent *event) { qDebug() << "mouse leave."; } void MyLabel::mousePressEvent(QMouseEvent *ev) { qDebug() << "mouse pressed."; QString str1 = QString("mouse pressed at position(%1, %2).").arg(ev->x()).arg(ev->y()); qDebug() << str1; } void MyLabel::mouseReleaseEvent(QMouseEvent *ev) { qDebug() << "mouse released."; QString str1; str1.sprintf("mouse released at position(%1, %2).", ev->x(), ev->y()); qDebug() << str1; } void MyLabel::mouseMoveEvent(QMouseEvent *ev) { qDebug() << "mouse moved."; QString str1 = QString("mouse moved at position(%1, %2).").arg(ev->x()).arg(ev->y()); qDebug() << str1; }
MyLabel继承了QLabel,覆盖了mousePressEvent()、mouseMoveEvent()和MouseReleaseEvent()三个函数。并没有添加什么功能,只是在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)的时候,把当前鼠标的坐标值打印出来。
QString的arg()函数可以自动替换掉QString中出现的占位符。其占位符以 % 开始,后面是占位符的位置,例如 %1,%2 这种。
QString("[%1, %2]").arg(x).arg(y);
语句将会使用x替换 %1,y替换 %2,因此,生成的QString为[x, y]。
在mouseReleaseEvent()函数中,使用了另外一种QString的构造方法。类似 C 风格的格式化函数sprintf()来构造QString。
在上面的代码中,只有在点击鼠标之后移动鼠标,才能在mouseMoveEvent()函数中显示鼠标坐标值:
这是因为QWidget中有一个mouseTracking属性,该属性用于设置是否追踪鼠标。只有鼠标被追踪时,mouseMoveEvent()才会发出。如果mouseTracking是 false(默认),组件在至少一次鼠标点击之后,才能够被追踪(发出mouseMoveEvent()事件)。如果mouseTracking为 true,则mouseMoveEvent()直接可以被发出。
可以在main()函数中添加如下代码:label->setMouseTracking(true);
这样鼠标移动操作就可以直接被捕获,而不用先点击鼠标了。
MyLabel::MyLabel(QWidget *parent) : QLabel(parent) {
setMouseTracking(true);//设置鼠标追踪状态
}
在需要利用MyLabel控件的ui界面中,加入Label控件,并有点点击将其提升为MyLabel控件,从而重写的各种方法可以该控件上起作用,
最后达到捕获鼠标事件的效果,
由定时器timer定时触发的时间叫做timerEvent定时器事件,
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
//重写定时器的事件
void timerEvent(QTimerEvent *);
int tid1;
int tid2;
int tid3;
private:
Ui::MainWindow *ui;
};
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); tid1 = startTimer(1000); tid2 = startTimer(2000); tid3 = startTimer(3000); } void MainWindow::timerEvent(QTimerEvent *ev) { if (ev->timerId() == tid1) { static int num1 = 1; ui->label2->setText(QString::number(num1++)); } else if (ev->timerId() == tid2) { static int num2 = 1; ui->label3->setText(QString::number(num2++)); } else if (ev->timerId() == tid3) { static int num3 = 1; ui->label4->setText(QString::number(num3++)); } }
定时器的使用除了timerEvent的方式,还有一个专门的定时器类QTimer,该类非常的强大提供了很多的功能,
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
QTimer *timer = new QTimer(this);
timer->start(500);
connect(timer, &QTimer::timeout, this, [=](){
static int num4 = 1;
ui->label5->setText(QString::number(num4++));
});
connect(ui->btn, &QPushButton::clicked, this, [=](){
if (timer->isActive()) timer->stop();
else timer->start();
});
}
事件分发eventDispatch
事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器event handler(event()函数主要用于事件的分发)。
如果开发者希望在事件不向下分发之,而是做一些自定义的操作,则需要重写event()函数。
通过自定义的event函数处理过感兴趣的事件之后,可以直接返回 true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的event()函数继续转发,否则这个组件就只能处理我们定义的事件了。
例1:要在某个QWidget组件中监听鼠标点击的按下,就可以继承QWidget并重写它的event()函数,截断事件分发的流程来达到目的:
void MyLabel::mousePressEvent(QMouseEvent *ev) { qDebug() << "mouse pressed."; QString str1 = QString("mouse pressed at position(%1, %2).").arg(ev->x()).arg(ev->y()); qDebug() << str1; } bool MyLabel::event(QEvent *e) { //如果ev->typy为鼠标按下事件 则在event事件分发中做拦截操作 if (e->type() == QEvent::MouseButtonPress) { QMouseEvent *ev = static_cast<QMouseEvent *>(e); QString str1 = QString("event dispatch catched: mouse pressed at position(%1, %2).").arg(ev->x()).arg(ev->y()); qDebug() << str1; return true;//true代表用户自己处理不向下分发 } //其他的事件交给父类处理 默认处理 return QLabel::event(e); }
例2:要在某个QWidget组件中监听 tab 键的按下,就可以继承QWidget并重写它的event()函数,来达到这个目的:
bool CustomWidget::event(QEvent *e) {
if (e->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;
}
}
return QWidget::event(e);
}
CustomWidget是QWidget的子类,这里重写了其event()函数,event函数有一个QEvent对象作为参数(需要转发的事件对象)。
可以通过使用QEvent::type()函数可以检查事件的实际类型,其返回值是QEvent::Type类型的枚举。
Qt的event函数实际上本质是使用QEvent::type()判断事件类型,然后根据类型调用对应事件处理器,
其实际是通过事件处理器来响应一个具体的事件,这相当于event()函数将具体事件的处理“委托”给具体的事件处理器。
而这些事件处理器是 protected virtual 的,因此只要重写了某个事件处理器,即可让 Qt 调用开发者自己实现的版本。由此可以见,event()是一个集中处理不同类型的事件的地方。
switch (event->type()) {
case QEvent::MouseMove:
mouseMoveEvent((QMouseEvent*)event);
break;
// ...
}
结论:
有时候对象需要查看、甚至要拦截发送到另外对象的事件。如对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。Qt 创建了QEvent事件对象之后,会调用QObject的event()函数处理事件的分发,显然可以在event()函数中实现拦截的操作。
event()函数会有两个问题:
这两个问题是event()函数无法处理的,于是Qt 提供了另外一种机制来解决:事件过滤器。
QObject的eventFilter()函数,用于建立事件过滤器,函数原型如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
函数解释:
事件过滤器使用步骤:1.为控件安装事件过滤器,2.重写eventFilter事件,
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); //重写事件过滤器 bool eventFilter(QObject *watched, QEvent *event); private: Ui::MainWindow *ui; }; MainWindow::MainWindow(QWidget *parent):QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //步骤1 为label1安装事件过滤器 ui->label1->installEventFilter(this);//传递需要安装事件过滤器的父类 } //步骤2 重写eventfilter事件 bool MainWindow::eventFilter(QObject *watched, QEvent *event) { if (watched == ui->label1 && event->type() == QEvent::MouseButtonPress) { QMouseEvent *ev = static_cast<QMouseEvent*>(event); QString str1 = QString("eventFilter: mouse pressed at position(%1, %2).").arg(ev->x()).arg(ev->y()); qDebug() << str1; return true; } return QMainWindow::eventFilter(watched, event); }
重写了MainWindow类中的eventFilter()函数,为了过滤特定组件上的事件,首先判断该对象是否为感兴趣的组件,然后判断这个事件的类型。watched == ui->label1 && event->type() == QEvent::MouseButtonPress
对鼠标点击事件进行拦截,如果这个事件是目标事件则进行自定义操作后直接返回 true(过滤掉了这个事件),
其他事件需要继续处理所以返回 false。对于其它的组件并不能保证是否还有过滤器,于是最保险的方式是调用父类的函数。
安装过滤器需要调用QObject::installEventFilter()函数。函数的原型如下:
void QObject::installEventFilter(QObject * filterObj);
该函数接受一个QObject *类型的参数,eventFilter()函数是QObject的一个成员函数,因此任意QObject都可以作为事件过滤器(如果没有重写eventFilter()函数,这个事件过滤器是没有任何作用的,默认什么都不会过滤)。已经存在的过滤器则可以通过QObject::removeEventFilter()函数移除。
可以向1个对象上面安装多个事件处理器,只要调用多次installEventFilter()函数。如果一个对象存在多个事件过滤器,那么最后一个安装的会第一个执行,即后进先执行的顺序。
特别注意:事件处理器必须在同一线程才能使用生效,
QCoreApplication::notify():
事件拦截实际上还有一种方法,Qt 事件的调用最终都会追溯到QCoreApplication::notify()函数,因此最大的控制权实际上是重写QCoreApplication::notify()
。该函数声明:
virtual bool QCoreApplication::notify(QObject *receiver, QEvent *event);
该函数会将event发送给receiver,也就是调用receiver->event(event),其返回值就是来自receiver的事件处理器。
该函数为任意线程的任意对象的任意事件调用,因此不存在事件过滤器的线程的问题。不过并不推荐这么做,因为notify()函数只有一个,而事件过滤器要灵活得多。
Qt 的事件处理,实际上是有五个层次:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。