目录
1、事件和信号
图形界面应用程序的消息处理模型
系统内核将用户操作翻译成对应的程序消息 ,GUI程序在运行时会创建一个消息队列 ,程序在运行过程中需要实时处理队列中的消息 ,当队列中没有消息时,程序将处于停滞状态
操作系统发送的消息如何转变成Qt信号?
Qt平台将系统产生的消息转换为Qt事件
- Qt事件是一个QEvent的对象,Qt事件用于描述程序内部或外部发生的动作
- 任意的QObject对象都具备事件处理的能力 :输入事件,拖拽事件,操作系统绘制GUI事件,关闭事件,计时器事件 ...
GUI应用程序的事件处理方式
1. Qt事件产生后立即被分发到QWdget对象 ,QWidget中的 event(QEvent*) 函数进行事件处理
2. event(QEvent*) 根据事件类型调用不同的事件处理函数 ,在事件处理函数中发送Qt中预定义的信号
3. 调用信号关联的槽函数
场景分析:按钮点击
QPushButton事件处理分析
1、接收到鼠标点击事件
2、调用event(QEvent*)成员函数
3、调用mouseReleaseEvent(QMouseEvent*)成员函数
4、调用click()成员函数
5、触发信号SIGNAL(clicked())
QMyPushButton.h
#ifndef _QMYPUSHBUTTON_H_
#define _QMYPUSHBUTTON_H_
#include <QPushButton>
typedef void (QButtonListener)(QObject*, QMouseEvent*);
class QMyPushButton : public QPushButton
{
Q_OBJECT
protected:
QButtonListener* m_listener;
//重写鼠标释放事件处理函数,事件分配后会被调用
void mouseReleaseEvent(QMouseEvent *e);
public:
explicit QMyPushButton(QWidget* parent = 0, QButtonListener* listener = 0);
signals:
public slots:
};
#endif // _QMYPUSHBUTTON_H_
QMyPushButton.cpp
#include "QMyPushButton.h"
#include <QMouseEvent>
QMyPushButton::QMyPushButton(QWidget* parent, QButtonListener* listener) : QPushButton(parent)
{
m_listener = listener;
}
//按钮被点击时事件传过来时会调用这个重写的事件处理函数
void QMyPushButton::mouseReleaseEvent(QMouseEvent *e)
{
if( m_listener != NULL )
{
m_listener(this, e); // onMyButtonMouseRelease,没有像预定义的那样发送一个信号
e->accept(); // 标记当前事件已经被处理
setDown(false);// 更新UI,按钮弹起(否则按钮会一直显示被按着状态)
}
else
{
// 调用默认处理函数,会发送预定义的信号,对应槽函数正常调用
QPushButton::mouseReleaseEvent(e);
}
}
Widget.h
#ifndef _WIDGET_H_
#define _WIDGET_H_
#include <QtGui/QWidget>
#include "QMyPushButton.h"
class Widget : public QWidget
{
Q_OBJECT
QMyPushButton myButton;
protected slots:
void onMyButtonClicked();
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // _WIDGET_H_
Widget.cpp
#include "Widget.h"
#include <QDebug>
void onMyButtonMouseRelease(QObject* sender, QMouseEvent* e)
{
qDebug() << "onMyButtonMouseRelease(QObject* sender, QMouseEvent* e)";
}
// 初始化列表中用onMyButtonMouseRelease初始化m_listener
Widget::Widget(QWidget *parent) : QWidget(parent), myButton(this, onMyButtonMouseRelease)
{
myButton.setText("QMyPushButton");
connect(&myButton, SIGNAL(clicked()), this, SLOT(onMyButtonClicked()));
}
void Widget::onMyButtonClicked()
{
qDebug() << "onMyButtonClicked()";
}
Widget::~Widget()
{
}
main.cpp
#include <QtGui/QApplication>
#include "Widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
事件(QEvent)和信号(SIGNAL)不同
-事件由具体对象进行处理 ,信号由具体对象主动产生
-改写事件处理函数可能导致程序行为发生改变,信号是否存在对应的槽函数不会改变程序行为
-一般而言,信号在具体的事件处理函数中产生
2、事件处理的顺序
事件被组件对象处理后可能传递到其父组件对象
QEvent中的关键成员函数
-void ignore() :接收者忽略当前事件,事件可能传递给父组件
-void accept() :接收者期望处理当前事件
-bool isAccepted() :判断当前事件是否被处理
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
#include "MyLineEdit.h"
class Widget : public QWidget
{
Q_OBJECT
MyLineEdit myLineEdit;
public:
Widget(QWidget* parent = 0);
bool event(QEvent* e);
void keyPressEvent(QKeyEvent* e);
~Widget();
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include <QDebug>
#include <QEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
// Qt事件被event函数处理
bool Widget::event(QEvent* e)
{
// 当处理的事件是按键事件
if( e->type() == QEvent::KeyPress )
{
qDebug() << "Widget::event";
}
// 直接调用父类事件处理函数
return QWidget::event(e); // 根据事件的类型调用keyPressEvent()事件处理函数处理
}
void Widget::keyPressEvent(QKeyEvent* e)
{
qDebug() << "Widget::keyPressEvent";
// 调用父类的事件处理函数
QWidget::keyPressEvent(e);
}
Widget::~Widget()
{
}
MyLineEdit.h
#ifndef MYLINEEDIT_H
#define MYLINEEDIT_H
#include <QLineEdit>
class MyLineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit MyLineEdit(QWidget *parent = 0);
bool event(QEvent* e);
void keyPressEvent(QKeyEvent* e);
signals:
public slots:
};
#endif // MYLINEEDIT_H
MyLineEdit.cpp
#include "MyLineEdit.h"
#include <QDebug>
#include <QEvent>
#include <QKeyEvent>
MyLineEdit::MyLineEdit(QWidget *parent) :
QLineEdit(parent)
{
}
bool MyLineEdit::event(QEvent* e)
{
if( e->type() == QEvent::KeyPress )
{
qDebug() << "MyLineEdit::event";
}
return QLineEdit::event(e);
}
void MyLineEdit::keyPressEvent(QKeyEvent* e)
{
qDebug() << "MyLineEdit::keyPressEvent";
QLineEdit::keyPressEvent(e);
// e->ignore();告诉Qt平台事件没有被处理,会调用父组件event函数处理
}
main.cpp
#include <QtGui/QApplication>
#include "Widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Qt事件被分配给MyLineEdit,调用自身event函数进行事件处理,根据事件类型不同调用不同的事件处理函数
当取消 e->ignore(); 的注释, 还会调用了父组件的event函数处理事件,即事件又被传给了父组件
3、Qt中的事件过滤器
Qt中的事件过滤器
-事件过滤器可以对其他组件接收到的事件进行监控(例如可以没收事件)
-组件通过installEventFilter()函数安装事件过滤器 (即组件被事件过滤器监控着)
-任意的QObject对象都可以作为事件过滤器使用,事件过滤器对象需要重写eventFilter()函数
-事件过滤器对象在组件之前接收到事件 ,事件过滤器能够决定是否将事件转发到组件对象
沿用上面实验的代码改进
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "MyLineEdit.h"
class Widget : public QWidget
{
Q_OBJECT
MyLineEdit myLineEdit;
public:
Widget(QWidget* parent = 0);
bool event(QEvent* e);
void keyPressEvent(QKeyEvent* e);
//重写eventFilter, 所以当前类对象是事件过滤器对象
bool eventFilter(QObject* obj, QEvent* e);
~Widget();
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include <QDebug>
#include <QEvent>
#include <QKeyEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent),myLineEdit(this)
{
myLineEdit.installEventFilter(this); // 事件过滤器对象(Widget)安装到感兴趣的组件对象
}
bool Widget::event(QEvent* e)
{
if( e->type() == QEvent::KeyPress )
{
qDebug() << "Widget::event";
}
return QWidget::event(e);
}
void Widget::keyPressEvent(QKeyEvent* e)
{
qDebug() << "Widget::keyPressEvent";
QWidget::keyPressEvent(e);
}
//事件过滤对象重写eventFilter函数
bool Widget::eventFilter(QObject* obj, QEvent* e)
{
bool ret = true; // 返回值为true,表示事件已经处理不会传递给obj了
// 当被监控组件为myLineEdit且事件为按下按键
if( (obj == &myLineEdit) && (e->type() == QEvent::KeyPress) )
{
qDebug() << "Widget::eventFilter";
QKeyEvent* evt = dynamic_cast<QKeyEvent*>(e);
switch(evt->key()) // 当键入数字时,事件被myLineEdit正常接收,其它都被没收
{
case Qt::Key_0:
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
ret = false; // false事件正常传递给obj
break;
default:
break;
}
}
else
{
// 直接调用父类eventFilter函数
ret = QWidget::eventFilter(obj, e);
}
return ret;
}
Widget::~Widget()
{
}
当按下除数字以外的键时
发现字母等都被过滤,MyLineEdit对象没有接收到事件
当按下数字键时
4、Qt中的拖放事件
拖放一个文件进入窗口时将触发拖放事件 ,每一个QWidget对象都能够处理拖放事件
拖放事件的处理函数为
- void dragEnterEvent(QDragEnterEvent* e);
- void dropEvent(QDropEvent* e);
拖放事件中的QMimeData
- QMimeData是Qt中的多媒体数据类 ,QMimeData支持多种不同类型的多媒体数据
- 拖放事件通过QMimeData对象传递数据
常用MIME类型数据处理函数
自定义拖放事件的步骤
1. 对接收拖放事件的对象调用 setAcceptDrops 成员函数
2. 重写 dragEnterEvent 函数并判断MIME类型
期望数据 : e- > acceptProposedAction ()
其它数据 : e- > ignore();
3. 重写dropEvent函数并判断MIME类型
期望数据:从事件对象中获取MIME数据并处理
其它数据: e- > ignore();
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
class Widget : public QWidget
{
Q_OBJECT
protected:
void dragEnterEvent(QDragEnterEvent* e);
void dropEvent(QDropEvent* e);
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QDebug>
#include <QList>
#include <QMimeData>
#include <QUrl>
Widget::Widget(QWidget *parent) : QWidget(parent)
{
setAcceptDrops(true); // 当前对象可以接受拖放事件
}
// 重写托的事件处理函数
void Widget::dragEnterEvent(QDragEnterEvent* e)
{
if( e->mimeData()->hasUrls() ) // 判断MIME数据是路径类型
{
e->acceptProposedAction();
}
else
{
e->ignore(); // 交给父组件对象处理
}
}
// 重写放的事件处理函数
void Widget::dropEvent(QDropEvent* e)
{
if( e->mimeData()->hasUrls() )
{
QList<QUrl> list = e->mimeData()->urls();
for(int i=0; i<list.count(); i++)
{
qDebug() << list[i].toLocalFile(); // 将当前url转换为本地路径
}
}
else
{
e->ignore();
}
}
Widget::~Widget()
{
}
5、发送预定义事件
Qt中可以在程序中自主发送事件
-阻塞型事件发送 :事件发送后需要等待事件处理完成
-非阻塞型事件发送 :事件发送后立即返回 ,事件被发送到事件队列中等待处理
QApplication类提供了支持事件发送的静态成员函数
-阻塞型发送函数: bool sendEvent(QObject* receiver, QEvent* event);
-非阻塞型发送函数: bool postEvent(QObject* receiver, QEvent* event);
-sendEvent中事件对象的生命期由Qt程序管理 ,同时支持栈事件对象和堆事件对象的发送
-postEvent中事件对象的生命期由Qt平台管理 ,只能发送堆事件对象 ,事件被处理后由Qt平台销毁
使用sendEvent发送事件对象
消息发送过程可以理解为:在sendEvent()函数内部直接调用Qt对象的event()事件处理函数。
这样event函数return,sendEvent才能return
使用postEvent发送事件对象
Widgte.cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
#include <QPushButton>
class Widget : public QWidget
{
Q_OBJECT
QPushButton m_pushButton;
void testSendEvent();
void testPostEvent();
protected slots:
void onButtonClicked();
public:
Widget(QWidget *parent = 0);
bool event(QEvent* evt);
~Widget();
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include <QMouseEvent>
#include <QApplication>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_pushButton.setParent(this);
m_pushButton.setText("Test");
connect(&m_pushButton, SIGNAL(clicked()), this, SLOT(onButtonClicked()));
}
void Widget::onButtonClicked()
{
// testSendEvent();
// testPostEvent();
}
void Widget::testSendEvent()
{
// 0,0位置鼠标左键双击事件,没有按键盘任意键
QMouseEvent dbcEvt(QEvent::MouseButtonDblClick, QPoint(0, 0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
qDebug() << "Before sendEvent()";
QApplication::sendEvent(this, &dbcEvt); // 将事件发送给当前Widget对象
qDebug() << "After sendEvent()";
}
void Widget::testPostEvent()
{
QMouseEvent* dbcEvt = new QMouseEvent(QEvent::MouseButtonDblClick, QPoint(0, 0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
qDebug() << "Before postEvent()";
QApplication::postEvent(this, dbcEvt);
// 在点击按钮后会产生一个点击事件,在onButtonClicked()槽函数内部调用testPostEvent()
// 产生一个新事件,当然不会立即处理,会放入事件队列,等点击事件处理完后再处理
qDebug() << "After postEvent()";
}
bool Widget::event(QEvent* evt)//重写event()函数
{
if( evt->type() == QEvent::MouseButtonDblClick )
{
qDebug() << "event(): " << evt;
}
return QWidget::event(evt);
}
Widget::~Widget()
{
}
6、自定义事件对象
Qt可以自定义新的事件类
-自定义的事件类必须继承自QEvent ,必须拥有全局唯一的Type值
-程序中必须提供处理自定义事件对象的方法
class StringEvent : public QEvent //自定义事件类
{
QString m_data;
public:
const static Type TYPE = static_cast<Type>(QEvent::User + 0xFF);
// enum QEvent::Type This enum type defines the valid event types in Qt.
};
Qt中事件的Type值
-每个事件类都拥有全局唯一的Type值,自定义事件类的Type值也需要自定义
-自定义事件类使用QEvent::User之后的值作为Type值,程序中保证QEvent::User + VALUE全局唯一即可
处理自定义事件对象的方法
-1. 将事件过滤器安装到目标对象:在eventFilter()函数中编写自定义事件的处理逻辑
-2. 在目标对象的类中重写事件处理函数:在event()函数中编写自定义事件的处理逻辑
StringEvent.h
#ifndef _STRINGEVENT_H_
#define _STRINGEVENT_H_
#include <QEvent>
#include <QString>
class StringEvent : public QEvent //自定义事件类
{
QString m_data;
public:
const static Type TYPE = static_cast<Type>(QEvent::User + 0xFF);
explicit StringEvent(QString data = "");
QString data();
};
#endif // _STRINGEVENT_H_
StringEvent.cpp
#include "StringEvent.h"
StringEvent::StringEvent(QString data) : QEvent(TYPE)
{
m_data = data;
}
QString StringEvent::data()
{
return m_data;
}
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
#include <QLineEdit>
class Widget : public QWidget
{
Q_OBJECT
QLineEdit m_edit;
public:
Widget(QWidget *parent = 0);
bool event(QEvent* evt);
bool eventFilter(QObject* obj, QEvent* evt); // 将当前窗口作为事件过滤器
~Widget();
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include "StringEvent.h"
#include <QMouseEvent>
#include <QDebug>
#include <QApplication>
Widget::Widget(QWidget *parent)
: QWidget(parent), m_edit(this)
{
m_edit.installEventFilter(this);//监控文本框对象
}
bool Widget::event(QEvent* evt)//重写event事件处理函数
{
if( evt->type() == QMouseEvent::MouseButtonDblClick ) // 双击窗口任意空白区,双击事件
{
qDebug() << "event: Before sentEvent";
StringEvent e("DT");
QApplication::sendEvent(&m_edit, &e); // 发送自定义事件到文本框对象
qDebug() << "event: After sentEvent";
}
return QWidget::event(evt);
}
bool Widget::eventFilter(QObject* obj, QEvent* evt)
{
if( (obj == &m_edit) && (evt->type() == StringEvent::TYPE) )
{
StringEvent* se = dynamic_cast<StringEvent*>(evt);
qDebug() << "Receive: " << se->data();
m_edit.insert(se->data());
return true; // 返回true,代表当前事件已被处理
}
return QWidget::eventFilter(obj, evt);
}
Widget::~Widget()
{
}