Qt 中的事件处理

目录

1、事件和信号

2、事件处理的顺序

3、Qt中的事件过滤器 

4、Qt中的拖放事件  

5、发送预定义事件

6、自定义事件对象 


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()
{
    
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值