Qt中事件循环机制详解
問題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<QKeyEvent *>(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 <QApplication>
?
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 <QApplication>
?
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 <QMouseEvent>
#include <QDebug>
?
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 <QWidget>
#include <QMouseEvent>
?
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 <QDebug>
?
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 <QApplication>
#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事件分發函數;
問題7.怎么使用自定義事件?
情景:自定義事件對于特定的操作是很有用的,定義一種連續點擊10次鼠標的事件NewMouseEvent,連續點擊10次屏幕喚醒屏幕校準程序。
自定義事件
newmouseevent.h
#ifndef MYEVENT_H
#define MYEVENT_H
?
#include <QEvent>
#include <QString>
?
class NewMouseEvent : public QEvent
{
public:
? ? explicit NewMouseEvent() : ?QEvent(MouseTenClick) {}
? ? const static Type MouseTenClick = static_cast<Type>(QEvent::User+0x10);
};
?
#endif // MYEVENT_H
widget.h
#ifndef MYEVENT_H
#define MYEVENT_H
?
#include <QEvent>
#include <QString>
?
class NewMouseEvent : public QEvent
{
public:
? ? explicit NewMouseEvent() : ?QEvent(MouseTenClick) {}
? ? const static Type MouseTenClick = static_cast<Type>(QEvent::User+0x10);
};
?
#endif // MYEVENT_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "newmouseevent.h"
#include <QMouseEvent>
#include <QTimer>
?
/*!
?* \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次鼠標算一次自定義事件
?
?
?
問題8:接受者對象中途被刪除會發生什么?被監聽者被刪除會怎么樣?
發送失敗?程序崩潰??
widget.h?
#ifndef WIDGET_H
#define WIDGET_H
?
#include <QWidget>
#include <QLabel>
?
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 <QTimer>
#include <QResizeEvent>
#include <QDebug>
?
/*!
?* \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;
}
?運行結果
?
總結
以上是生活随笔為你收集整理的Qt中事件循环机制详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: H264 CAVLC 研究
- 下一篇: Qt中为自己的程序建立一个消息循环