第84课 - 多线程与界面组件的通信

版权声明:课程笔记内容整理于狄泰软件 https://blog.csdn.net/qq_39654127/article/details/82021268

1、有趣的问题 

是否可以在子线程中创建界面组件? 

void TestThread::run()
{
    /* It is ERROR to create GUI elements in SUB THREAD */
    QWidget w;

    w.show();

    exec();
}
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
     TestThread* ptt = new TestThread();

     ptt->start();
}

ASSERT failure in QWidget: "Widgets must be created in the GUI thread."

GUI系统设计原则 

        所有界面组件的操作都只能在主线程中

        完成;因此,主线程也叫做UI线程! 

 

子线程如何对界面组件进行更新? 

 

解决方案-信号与槽 

         1. 在子线程类中定义界面组件的更新信号(updateUI) 

         2. 在主窗口类中定义更新界面组件的槽函数(setlnfo) 

         3. 使用异步方式连接更新信号到槽函数(updateUI → setlnfo

                • 子线程通过发射信号的方式更新界面组件 

                • 所有的界面组件对象只能依附于主线程 

                • 子线程不能直接操作界面组件,但是可以

                   通过信号与槽的机制间接操作界面组件。 

2、编程实验 

子线程中更新界面组件   84-1.pro 

UpdateThread.h

#ifndef UPDATETHREAD_H
#define UPDATETHREAD_H

#include <QThread>

class UpdateThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit UpdateThread(QObject *parent = 0);
    
signals:
    void updateUI(QString text);
    
};

#endif // UPDATETHREAD_H

UpdateThread.cpp

#include "UpdateThread.h"

UpdateThread::UpdateThread(QObject *parent) :
    QThread(parent)
{
}

void UpdateThread::run()
{
    emit updateUI("Begin");

    for(int i=0; i<10; i++)
    {
        emit updateUI(QString::number(i));

        sleep(1);
    }

    emit updateUI("End");
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include "TestThread.h"
#include "UpdateThread.h"

class Widget : public QWidget
{
    Q_OBJECT

    UpdateThread m_thread;
    QPlainTextEdit textEdit;
protected slots:
    void appendText(QString text);
public:
    Widget(QWidget *parent = 0);
    ~Widget();
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "TestThread.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    textEdit.setParent(this);
    textEdit.move(20, 20);
    textEdit.resize(200, 150);
    textEdit.setReadOnly(true);

    connect(&m_thread, SIGNAL(updateUI(QString)), this, SLOT(appendText(QString)));

    m_thread.start();

}

void Widget::appendText(QString text)
{
    textEdit.appendPlainText(text);
}

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

本质分析 

                子线程发射信号通知主线程界面更新请求;主线程

                根据具体信号以及信号参数对界面组件进行修改。 

3、进一步的思考 

是否有其它间接的方式可以让子线程更新界面组件的状态? 

解决方案-发送自定义事件 

        1. 自定义事件类用于描述界面更新细节 

        2. 在主窗口类中重写事件处理函数event 

        3. 使用postEvent函数(异步方式)发送自定义事件类对象 

                 ★ 子线程指定接受消息的对象为主窗口对象 

                 ★ 在event事件处理函数更新界面状态 

                                              事件对象是在主线程中被处理的,即:

                                              event函数的调用在主线程中完成。 

 

3、编程实验 

子线程中更新界面组件  85-1.pro 

自定义事件类完全复用第44课 - 发送自定义事件中的StringEvent类

UpdateThread.cpp

#include "UpdateThread.h"
#include <QApplication>
#include "StringEvent.h"

UpdateThread::UpdateThread(QObject *parent) :
    QThread(parent)
{
}

void UpdateThread::run()
{
    // emit updateUI("Begin");
    QApplication::postEvent(parent(), new StringEvent("Begin")); //发送自定义事件

    for(int i=0; i<10; i++)
    {
        // emit updateUI(QString::number(i));
        QApplication::postEvent(parent(), new StringEvent(QString::number(i)));

        sleep(1);
    }

    // emit updateUI("End");
    QApplication::postEvent(parent(), new StringEvent("End"));
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include "UpdateThread.h"

class Widget : public QWidget
{
    Q_OBJECT

    UpdateThread m_thread;
    QPlainTextEdit textEdit;

public:
    Widget(QWidget *parent = 0);
    bool event(QEvent *evt); //自定义事件处理函数
    ~Widget();
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "StringEvent.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{

    textEdit.setParent(this);
    textEdit.move(20, 20);
    textEdit.resize(200, 150);
    textEdit.setReadOnly(true);

    m_thread.setParent(this);//让m_thread包含父类指针,以便线程开始向其后发送事件
    m_thread.start();
}

bool Widget::event(QEvent *evt)
{
    bool ret = true;

    if( evt->type() == StringEvent::TYPE )
    {
        StringEvent* se = dynamic_cast<StringEvent*>(evt);

        if( se != NULL )
        {
            textEdit.appendPlainText(se->data());
        }
    }
    else
    {
        ret = QWidget::event(evt);
    }

    return ret;
}

Widget::~Widget()
{
    
}

4、小结 

现代GUI平台只允许在主线程中直接操作界面组件 

Qt中可以借助信号与槽的机制在子线程中操作界面组件 

进行信号与槽的连侯时必须采用异步连接的方式 

界面组件对象必须依附于主线程 

Qt中可以发送自定义事件在子线程中操作界面组件 

必须使用postEvent函数进行事件发送(异步方式) 

发送的事件对象必须在堆上创建 

子线程创建时必须附带目标对象的地址信息 

猜你喜欢

转载自blog.csdn.net/qq_39654127/article/details/82021268