问题1:Qt中常见的事件有哪些?
答:鼠标事件(QMouseEvent)、键盘事件(QKeyEvent)、绘制事件(QPaintEvent)、窗口尺寸改变(QResizeEvent)、滚动事件(QScrollEvent)、控件显示(QShowEvent)、控件隐藏(QHideEvent)、定时器事件(QTimerEvent)等等。。
问题2:Qt是事件驱动的,这句话该怎么理解呢?
Qt将系统产生的信号(软件中断)转换成Qt事件,并且将事件封装成类,所有的事件类都是由QEvent派生的,事件的产生和处理就是Qt程序的主轴,且伴随着整个程序的运行周期。因此我们说,Qt是事件驱动的。
问题3:Qt事件是由谁产生的?Qt是如何将信号转换成事件的?
答:Qt的官方手册说,事件有两个来源:程序外部和程序内部,多数情况下来自操作系统并且通过spontaneous()函数返回true来获知事件来自于程序外部,当spontaneous()返回false时说明事件来自于程序内部,就像例程1创建一个事件并把它分发出去。
问题4:Qt事件是由谁接收的?
答:QObject!它是所有Qt类的基类!是Qt对象模型的核心!QObject类的三大核心功能其中之一就是:事件处理。QObject通过event()函数调用获取事件。所有的需要处理事件的类都必须继承自Qobject,可以通过重定义event()函数实现自定义事件处理或者将事件交给父类。
问题5:事件处理的流程是什么样的?
答:事件有别于信号的重要一点:事件是一个类对象具有特定的类型,事件多数情况下是被分发到一个队列中(事件队列),当队列中有事件时就不停的将队列中的事件发送给QObject对象,当队列为空时就阻塞地等待事件,这个过程就是事件循环!
QCoreApplication::exec()开启了这种循环,一直到QCoreApplication::exit()被调用才终止,所以说事件循环是伴随着Qt程序的整个运行周期!
另外一种同步处理情形是通过sendEvent()将事件发送出去,直接进入事件的传送和处理流程。
事件处理流程如图所示:
例程1:sendEvent同步事件分发
/*!
* \brief Widget::Widget 使用sendEvent同步分发事件
* 使用QPushButton模拟键盘的回删和向前删除按键
* \param parent
*/
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_button_back_clicked()
{
int key = Qt::Key_Backspace; //
QKeyEvent EventPress(QEvent::KeyPress, key, Qt::NoModifier);
QApplication::sendEvent(ui->textEdit, &EventPress);
QKeyEvent EventRelease(QEvent::KeyRelease, key, Qt::NoModifier);
QApplication::sendEvent(ui->textEdit, &EventRelease);
}
void Widget::on_button_delete_clicked()
{
int key = Qt::Key_Delete; //
QKeyEvent EventPress(QEvent::KeyPress, key, Qt::NoModifier);
QApplication::sendEvent(ui->textEdit, &EventPress);
QKeyEvent EventRelease(QEvent::KeyRelease, key, Qt::NoModifier);
QApplication::sendEvent(ui->textEdit, &EventRelease);
}
postEvent和sendEvent的关系就像Qt::QueuedConnection和Qt::DirectConnection的关系,只不过前两者是分发事件后两者是发送消息罢了,机制上postEvent和QueuedConnected是异步通信,而另外两种是同步通信。
例程2:postEvent异步事件分发
int count = 0;
/*!
* \brief Widget::Widget 使用postEvent异步分发事件
* 连续分发10个事件,在事件处理函数中逐个处理
* \param parent
*/
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
int i = 1;
while(i <= 10)
{
//postEvent传递的事件必须是通过new创建的
qDebug() << "分发第" << i << "个事件";
QApplication::postEvent(this, new QEvent(NewType));
i++;
}
}
void Widget::customEvent(QEvent *event)
{
//使用延时模拟处理过程
if(event->type() == NewType)
{
qDebug() << "现在时间:" <<
QTime::currentTime().toString("hh::mm:ss.zzz");
qDebug() << "第" << ++count << "次收到了事件!处理事件需要一点时间!";
Delay(1000*2);
}
QWidget::customEvent(event);
}
Widget::~Widget()
{
delete ui;
}
void Widget::Delay(unsigned int msec)
{
QTime start = QTime::currentTime();
QTime end;
do{
end = QTime::currentTime();
} while(start.msecsTo(end) <= msec);
}
问题6:事件过滤器机制?
事件的传送和处理流程的第一站是事件过滤器eventFilter(),某个对象A可以通过给另一个对象B安装事件处理器,实现对对象B事件的监听或者拦截功能。我们可以给A取名监听器,B取名接收器。一个对象可以监听多个对象,一个对象也可以被多个事件监听。事件过滤器返回true则表示事件已经处理完毕,否则传递给下一个监听器或者接收器本身。
例程3:事件过滤器
/*!
* \brief Widget::Widget 事件过滤器
* 不借助Tab键的情况下使用Space键实现控件跳转
* \param parent
*/
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
ui->lineEdit_user->setText(QString("lee"));
focusNextChild();
ui->lineEdit_password->setText(QString("*******"));
//监听控件
ui->lineEdit_user->installEventFilter(this);
ui->lineEdit_password->installEventFilter(this);
ui->button_accept->installEventFilter(this);
ui->button_cancel->installEventFilter(this);
}
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
//定义事件处理动作
if (watched == ui->lineEdit_user || watched == ui->lineEdit_password
|| watched == ui->button_accept || watched == ui->button_cancel)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *e = static_cast(event);
if(e->key() == Qt::Key_Space)
{
focusNextChild();
return true;
}
}
}
return QWidget::eventFilter(watched, event);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_button_cancel_clicked()
{
qApp->quit();
}
值得注意的一点是QCoreApplication虽然负责事件分发,但本身也是继承自QObject的,所以在分发事件之前,也要检查自身是否被别的对象安装了事件过滤器,事件过滤器可能会过滤掉一些事件不发布。
例程4:QCoreApplication安装事件过滤器
widget.cpp
/*!
* \brief Filter::eventFilter 用于监听qApp的监听器
* \return
*/
bool Filter::eventFilter(QObject *obj, QEvent *event)
{
//阻止所有的鼠标点击事件
if(event->type() == QEvent::MouseButtonPress)
{
qDebug() << "sorry everybody, I gonna filter all the mouse event!";
return true;
}
return QObject::eventFilter(obj,event);
}
/*!
* \brief Widget::Widget
* \param parent
*/
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::mousePressEvent(QMouseEvent *event)
{
qDebug() << "mouse press!";
QWidget::mousePressEvent(event);
}
main.c
#include "widget.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Filter filter;
a.installEventFilter(&filter);
Widget w;
w.show();
return a.exec();
}
也可以通过重新实现QCoreApplication的notify(),自定义对事件的处理动作。
例程5:QCoreApplication子类化并重写notify
newapplication.h
#ifndef NEWAPPLICATION_H
#define NEWAPPLICATION_H
#include
class NewApplication : public QApplication
{
public:
NewApplication(int argc, char **argv) : QApplication(argc,argv) {}
virtual bool notify(QObject *, QEvent *);
};
#endif // NEWAPPLICATION_H
newapplication.cpp
#include "newapplication.h"
#include
#include
bool NewApplication::notify(QObject *receiver, QEvent *event)
{
if(event->type() == QMouseEvent::MouseButtonPress)
{
qDebug() << "sorry everybody I gonna filter all the mouse press event";
return true;
}
return QApplication::notify(receiver,event);
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
protected:
void mousePressEvent(QMouseEvent *event);
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::mousePressEvent(QMouseEvent *event)
{
qDebug() << "I am mainwindow Widget, I got a mouse event!";
QWidget::mousePressEvent(event);
}
void Widget::on_pushButton_clicked()
{
qDebug() << "I am push button , I got a mouse event!";
}
main.cpp
#include "widget.h"
#include
#include "newapplication.h"
int main(int argc, char *argv[])
{
// QApplication a(argc, argv);
NewApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
运行结果:点击界面的任意位置,事件都被qApp过滤。
小结:事件处理的方式
1.重新实现对象的特定事件处理函数,例如mousePressEvent、keyPressEvent 、showEvent等,处理完毕后将事件交给父类;
2.重新实现event函数,处理完毕后将事件交给父类;
3.在对象上安装事件过滤器,让其他对象控制此对象的事件行为;
4.给主程序QCoreApplication安装事件过滤器,在调用notify进行事件分发之前,会根据过滤器判断对事件的处理(例如:丢弃);
5.子类化QCoreApplication,重新实现notify事件分发函数;
情景:自定义事件对于特定的操作是很有用的,定义一种连续点击10次鼠标的事件NewMouseEvent,连续点击10次屏幕唤醒屏幕校准程序。
自定义事件
newmouseevent.h
#ifndef MYEVENT_H
#define MYEVENT_H
#include
#include
class NewMouseEvent : public QEvent
{
public:
explicit NewMouseEvent() : QEvent(MouseTenClick) {}
const static Type MouseTenClick = static_cast(QEvent::User+0x10);
};
#endif // MYEVENT_H
widget.h
#ifndef MYEVENT_H
#define MYEVENT_H
#include
#include
class NewMouseEvent : public QEvent
{
public:
explicit NewMouseEvent() : QEvent(MouseTenClick) {}
const static Type MouseTenClick = static_cast(QEvent::User+0x10);
};
#endif // MYEVENT_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "newmouseevent.h"
#include
#include
/*!
* \brief Widget::Widget
* 创建并分发一种新的事件:鼠标连续点击10次
* \param parent
*/
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->installEventFilter(this);
ui->label->setText(tr("请连续点击屏幕以唤醒屏幕校准功能!"));
ui->label->adjustSize();
m_timer = new QTimer;
m_timer->setInterval(1000);
m_timer->start();
connect(m_timer, SIGNAL(timeout()), SLOT(clearCount()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() != Qt::LeftButton)
return;
if(m_timer->isActive())
m_timer->stop(); //如果计时器在运行,则停止然后重新开始
m_timer->start();
count++;
if(10 == count)
{
count = 0;
NewMouseEvent event;
qApp->sendEvent(ui->label, &event);
}
QWidget::mouseReleaseEvent(event);
}
bool Widget::eventFilter(QObject *obj, QEvent *event)
{
if(obj == ui->label && event->type()== NewMouseEvent::MouseTenClick)
{
ui->label->setText(tr("你连续点击了10次屏幕,校准程序正在启动!"));
ui->label->adjustSize();
return true;
}
return QWidget::eventFilter(obj,event);
}
void Widget::clearCount()
{
count = 0;
}
运行结果
连续点击10次鼠标算一次自定义事件
发送失败?程序崩溃?
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
protected:
private slots:
void slotSendEvent();
void deleteLabel();
private:
Ui::Widget *ui;
QLabel *m_label;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
/*!
* \brief Widget::Widget
* 在事件循环分发事件给接收者之前,接收者被删除
* \param parent
*/
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//创建小窗口
m_label = new QLabel(this);
m_label->setStyleSheet(QString("border:1px solid red;"));
m_label->setGeometry(QRect(0,0,200,100));
//在qApp发送事件之前销毁小窗口
QTimer::singleShot(1000, this, SLOT(deleteLabel()));
//qApp发送事件给小窗口
QTimer::singleShot(2000, this, SLOT(slotSendEvent()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::slotSendEvent()
{
QResizeEvent re(QSize(300,200), QSize(200,100));
qDebug() << "qApp发送了一个事件给小窗口!";
qApp->sendEvent(m_label, &re);
}
void Widget::deleteLabel()
{
qDebug() << "小窗口被销毁了!";
delete m_label;
m_label = NULL;
}
运行结果