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函数进行事件发送(异步方式)
发送的事件对象必须在堆上创建
子线程创建时必须附带目标对象的地址信息