Qt之事件循环源码剖析(一)-----源码面前了无秘密

Qt程序提供了事件循环机制,那么它是怎么工作的,工作原理又是什么呢?让我们通过qt源码揭开它神秘的面纱。

在开始之前,我们先思考下面几个问题:
1.事件循环是何时,以及如何启动的?
2.对于Windows,Qt是如何捕获事件(如:鼠标事件,键盘按键事件)?如何对捕获到的事件进行处理的?
3.如何将Windows消息转换为QEvent?
4.用户自定义的事件又是如何被处理的?

一、Windows消息机制简介

在这里插入图片描述
在Windows操作系统,所有的WIN32程序都建立在消息循环的基础之上,windows的消息机制可以分为以下几大步:

  1. 注册窗口过程处理函数
  2. 创建窗体
  3. show窗体
  4. 获取消息
  5. 翻译消息
  6. 分发消息至窗口处理函数

二、事件循环初始化

int main(int argc, char *argv[])
{
   
   
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

首先实例化QApplication对象,在构造QApplication时初始化threadData,如下所示:
在这里插入图片描述
父对象如果存在则使用父对象的threadData,表明和父对象使用同一个工作线程处理事件,反之通过QThreadData::current()拿到当前线程threadData。对于主事件循环来说,则通过QThreadData::current()拿到当前主线程,通过主线程处理事件。

class QThreadData
{
   
   
public:
    QThreadData(int initialRefCount = 1);
    ~QThreadData();

    static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
	
	/**
	* 省略 .......
	*/

public:
    int loopLevel;
    int scopeLevel;

    QStack<QEventLoop *> eventLoops;
    QPostEventList postEventList;  // 待处理的事件列表 
    QAtomicPointer<QThread> thread;  // 工作线程
    QAtomicPointer<void> threadId;   // 工作线程Id
    QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;  // 事件分发者,在不同平台下创建不同的事件分发者
    QVector<void *> tls;
    FlaggedDebugSignatures flaggedSignatures;
};

以windows为例,eventDispatcher的初始化:
在这里插入图片描述
事件分发者(eventDispatcher),初始化调用堆栈如上
Step 1

// src\corelib\kernel\qcoreapplication.cpp
QCoreApplication::QCoreApplication(int &argc, char **argv
#ifndef Q_QDOC
                                   , int _internal
#endif
                                   )
#ifdef QT_NO_QOBJECT
    : d_ptr(new QCoreApplicationPrivate(argc, argv, _internal))
#else
    : QObject(*new QCoreApplicationPrivate(argc, argv, _internal))
#endif
{
   
   
    d_func()->q_ptr = this;
    d_func()->init();  // 调用QCoreApplicationPrivate的init函数
#ifndef QT_NO_QOBJECT
    QCoreApplicationPrivate::eventDispatcher->startingUp();
#endif
}

程序在启动时,通过main函数实例化QApplication对象。在QCoreApplication构造函数中调用QCoreApplicationPrivate的init函数初始参数
Step 2

// src\corelib\kernel\qcoreapplication.cpp
void QCoreApplicationPrivate::init()
{
   
   
	/**
	* 省略 .......
	*/
    auto thisThreadData = threadData.loadRelaxed();
    eventDispatcher = thisThreadData->eventDispatcher.loadRelaxed();

    // otherwise we create one
    if (!eventDispatcher)
        createEventDispatcher();  // 调用createEventDispatcher创建事件分发者
    Q_ASSERT(eventDispatcher);
    
    if (!eventDispatcher->parent()) {
   
   
        eventDispatcher->moveToThread(thisThreadData->thread.loadAcquire());
        eventDispatcher->setParent(q);
    }

    thisThreadData->eventDispatcher = eventDispatcher; // 事件分发者赋值给threadData的eventDispatcher。当threadData.postEventList列表中有新加入的事件时,需要借助事件分发者向外投递一个消息,处理此事件
    eventDispatcherReady();
    /**
	* 省略 .......
	*/
}

在QCoreApplicationPrivate的init函数中,如果事件分发者没有创建则通过createEventDispatcher虚方发创建不同平台的事件分发者。
Step 3

// src\widgets\kernel\qapplication.cpp
void QApplicationPrivate::createEventDispatcher()
{
   
   
    QGuiApplicationPrivate::createEventDispatcher();
}

// src\gui\kernel\qguiapplication.cpp
void QGuiApplicationPrivate::createEventDispatcher()
{
   
   
    Q_ASSERT(!eventDispatcher);

    if (platform_integration == nullptr)
        createPlatformIntegration(); // 创建平台

    // The platform integration should not mess with the event dispatcher
    Q_ASSERT(!eventDispatcher);

    eventDispatcher = platform_integration->createEventDispatcher(); // 通过对应平台的事件分发者
}

上段代码需要注意的是createEventDispatcher是一个虚方法,并且在每个类中都有实现。在此处则调用是QApplicationPrivate的该方法。在此处创建QWindowsIntegration以及QWindowsGuiEventDispatcher
Step 4

// src\gui\kernel\qguiapplication.cpp
void QGuiApplicationPrivate::createPlatformIntegration()
{
   
   
    QHighDpiScaling::initHighDpiScaling();

    // Load the platform integration
    QString platformPluginPath = QString::fromLocal8Bit(qgetenv("QT_QPA_PLATFORM_PLUGIN_PATH"));
    
    /**
	* 代码省略 .......
	* 这部分代码通过从环境变量或者从命令行参数中解析平台名称,插件路径,插件名称
	*/

    init_platform(QLatin1String(platformName), platformPluginPath, platformThemeName, argc, argv); // 创建平台插件

    if (!icon.isEmpty())
        forcedWindowIcon = QDir::isAbsolutePath(icon) ? QIcon(icon) : QIcon::fromTheme(icon);
}
static void init_platform(const QString &pluginNamesWithArguments, const QString &platformPluginPath, const QString &platformThemeName, int &argc, char **argv)
{
   
   
    QStringList plugins = pluginNamesWithArguments.split(QLatin1Char(';'));
    QStringList platformArguments;
    QStringList availablePlugins = QPlatformIntegrationFactory::keys(platformPluginPath);
    for (const auto &pluginArgument : plugins) {
   
   
        // Split into platform name and arguments
        QStringList arguments = pluginArgument.split(QLatin1Char(':'));
        const QString name = arguments.takeFirst().toLower();
        QString argumentsKey = name;
        argumentsKey[0] = argumentsKey.at(0).toUpper();
        arguments.append(QLibraryInfo::platformPluginArguments(argumentsKey));

        // Create the platform integration.
        QGuiApplicationPrivate::platform_integration = QPlatformIntegrationFactory::create(name, arguments, argc, argv, platformPluginPath); // 通过插件工具,创建平台插件
        if (Q_UNLIKELY(!QGuiApplicationPrivate::platform_integration)) {
   
   
            if (availablePlugins.contains(name)) {
   
   
                qCInfo(lcQpaPluginLoading).nospace().noquote()
                        << "Could not load the Qt platform plugin \"" << name << "\" in \""
                        << QDir::toNativeSeparators(platformPluginPath) << "\" even though it was found.";
            } else {
   
   
                qCWarning(lcQpaPluginLoading).nospace().noquote()
                        << "Could not find the Qt platform plugin \"" << name << "\" in \""
                        << QDir::toNativeSeparators(platformPluginPath) << "\"";
            }
        } else {
   
   
            QGuiApplicationPrivate::platform_name = new QString(name);
            platformArguments = arguments;
            break;
        }
    }
    /**
	* 代码省略 .......
	*/
}

从环境变量或者从命令行参数中解析平台名称,插件路径,插件名称。将插件路径下的所有插件放入到列表中,遍历插件列表加载对应平台的插件。如在windows上则加载的是qwindows.dll
插件位于安装目录下如:C:\Qt\5.15.2\msvc2019_64\plugins\platforms
在这里插入图片描述
windows下qt程序包插件位于执行目录下的platforms目录下:
在这里插入图片描述
在linux平台下,通过QT_QPA_PLATFORM_PLUGIN_PATH环境变量加载平台插件(因为我是在交叉编译环境上运行,所以是qeglfs):
在这里插入图片描述
Step 5

// src\gui\kernel\qplatformintegrationfactory.cpp
QPlatformIntegration *QPlatformIntegrationFactory::create(const QString &platform, const QStringList &paramList, int &argc, char **argv, const QString &platformPluginPath)
{
   
   
#if QT_CONFIG(library)
    // Try loading the plugin from platformPluginPath first:
    if (!platformPluginPath.isEmpty()) {
   
   
        QCoreApplication::addLibraryPath(platformPluginPath);
      
Qt的事件过滤器 Qt事件模型个真正强大的特色是个QObject 的实例能够管理另个QObject 实例的事件。 让我们试着设想已经有了个CustomerInfoDialog的小部件。CustomerInfoDialog 包含系列QLineEdit. 现在,我们想用空格键来代替Tab,使焦点在这些QLineEdit间切换。 个解决的方法是子类化QLineEdit,重新实现keyPressEvent(),并在keyPressEvent()里调用focusNextChild()。像下面这样: void MyLineEdit::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Space) { focusNextChild(); } else { QLineEdit::keyPressEvent(event); } } 但这有个缺点。如果CustomerInfoDialog里有很多不同的控件(比如QComboBox,QEdit,QSpinBox),我们就必须子类化这么多控件。这是个烦琐的任务。 个更好的解决办法是: 让CustomerInfoDialog去管理他的子部件的按键事件,实现要求的行为。我们可以使用事件过滤器。 个事件过滤器的安装需要下面2个步骤: 1, 调用installEventFilter()注册需要管理的对象。 2,在eventFilter() 里处理需要管理的对象的事件。 般,推荐在CustomerInfoDialog的构造函数中注册被管理的对象。像下面这样: CustomerInfoDialog::CustomerInfoDialog(QWidget *parent) : QDialog(parent){ ... firstNameEdit->installEventFilter(this); lastNameEdit->installEventFilter(this); cityEdit->installEventFilter(this); phoneNumberEdit->installEventFilter(this); } 旦,事件管理器被注册,发送到firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit的事件将首先发送到eventFilter()。 下面是个 eventFilter()函数的实现: bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event) { if (target == firstNameEdit || target == lastNameEdit || target == cityEdit || target == phoneNumberEdit) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); if (keyEvent->key() == Qt::Key_Space) { focusNextChild(); return true; } } } return QDialog::eventFilter(target, event); } 在上面的函数中,我们首先检查目标部件是否是 firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit。接着,我们判断事件是否是按键事件。如果事件是按键事件,我们把事件转换为QKeyEvent。接着,我们判断是否按下了空格键,如果是,我们调用focusNextChild(),把焦点传递给下个控件。然后,返回,true通知Qt,我们已经处理了该事件。 如果返回false的话,Qt继续将该事件发送给目标控件,结果是个空格被插入到QLineEdit中。 如果目标控件不是 QLineEdit,或者按键不是空格键,我们将把事件传递给基类的eventFilter()函数。 Qt提供5个级别
9.1事件机制原理分析 9.1.1 什么是Qt事件驱动?         我们在写Qt工程类项目的时候都会发现,主程序里面都有这么段代码: int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } 有点抽象,Qt进行了封装        实际上a.exec()便是Qt程序进入事件消息循环, 9.1.2 图形界面应用程序的消息处理模型 回调、os的魔抓windows、linux,从用户层到 内核层,如何管理进程、线程、 Os如何处理、底层机制 特点: 基于操作系统才能运行 GUI应用程序提供的功能必须由用户触发 用户操作界面时操作系统是第个感知的  系统内核的消息通过事件处理转变成QT的信号 9.1.3 Qt中的事件处理 (1)在Qt中,事件被封装成个个对象,所有的事件均继承自抽象类QEvent.              事件处理的核心包括事件①产生、②分发、③接受和处理 ①事件的产生 谁来产生事件? 最容易想到的是我们的输入设备,比如键盘、鼠标产生的 keyPressEvent,keyReleaseEvent, mousePressEvent,mouseReleaseEvent事件 (被封装成QMouseEvent和QKeyEvent)。 ②Qt中事件的分发 谁来负责分发事件? 对于non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类-Receiver.  对于Qt GUI程序,由QApplication来负责   ③事件的接受和处理 谁来接受和处理事件? 答案是QObject。 类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责( 内存管理、内省intropection、事件处理制)之。 任何个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。 9.1.4 QObject的内省机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值