首页 > 技术文章 > Qt C++ 子线程访问主线程数据和方法

leocc325 2020-05-09 21:23 原文

  因为项目需求,需要将模型中的数据保存为excel保存到电脑上,但是由于拉起excel这个过程需要几秒钟时间,如果在主线程中完成这项工作,那么这几秒钟程序会陷入假死状态,因此需要将其写到子线程中。

主线程:widget.h     widget.cpp

子线程:saveThread.h     saveThread.cpp

遇到的问题和解决方法记录如下

1:用于保存数据的函数 savedata_excel()已经在主线程中完成,而且可以正常调用。

那么我最初的想法就是只要在子线程中声明一个Widget对象就可以调用savedata_excel()函数,事实证明这样是不行的。编译器会提示savedata_excel()是私有成员,无法调用。

于是我想到了友元,经过学习之后成功的把saveThread声明为Widget的友元类,并且成功地调用了一个widget的测试函数(打印一串文字来测试是否成功)。但是在调用savedata_excel()函数时却依然失败了,遇到了下一个问题。

2:经过长时间的测试,找到原因是因为子线程中的widget对象没有分配内存(地址是0xcdcdcdcdcdcdcdcdcdcd),于是我在run()函数中添加语句Widget *=new Widget;给它分配内存地址,但是这次编译器又报错了widgets must be creat in the GUI thread,也就是说子线程是不能访问GUI对象的,这是真的把人憋坏了,又不能手动给它分配地址,有需要它有内存地址才能正确调用一些方法,这不是矛盾吗。

但是在不断的思索下,我终于还是找到了解决问题的办法:用信号和槽来把主线程中widget对象的地址传递给子线程中的widget对象,这样他们不就指向同一个地址了吗!

经验证,此方法确实可行,下面就是相关程序的代码:

 

SaveThread.h

#ifndef SAVETHREAD_H
#define SAVETHREAD_H
#include <QThread>
#include "widget.h"
#pragma execution_character_set("utf-8")
class SaveThread : public QThread
{
    Q_OBJECT
protected:
    void  run() Q_DECL_OVERRIDE;  //线程任务
public:
    SaveThread();
signals:
    void savedone();
private slots:
    void getaddress(Widget *);
private:
    Widget *w;
};

#endif // SAVETHREAD_H
SaveThread.cpp


#include "savethread.h"
#include <QDebug>
#include <QAxObject>
#include <QMessageBox>
#include <QFileDialog>

void SaveThread::run()
{
    qDebug()<<"子线程开始保存数据";
    QString strpath=w->fileName;
    strpath.append(w->shotnum);
    //qDebug()<<strpath;
    if(strpath!="")
    {
        //QMessageBox::information(this,tr("提示"),tr("正在保存数据,请稍等"));
        QAxObject *excel = new QAxObject;
        excel->setControl("Excel.APPlication");

        excel->dynamicCall("SetVisible (bool Visible)","false");//不显示窗体
        excel->setProperty("DisplayAlerts", false);//不显示任何警告信息。如果为true那么在关闭是会出现类似“文件已修改,是否保存”的提示
        QAxObject *workbooks = excel->querySubObject("WorkBooks");//获取工作簿集合
        workbooks->dynamicCall("Add");//新建一个工作簿
        QAxObject *workbook = excel->querySubObject("ActiveWorkBook");//获取当前工作簿
        QAxObject *worksheet = workbook->querySubObject("Worksheets(int)", 1);

        QAxObject *cell;
        for(int i = 0; i < 8; i++)
        {
            QString str="通道"+QString::number(i+1);
            cell=worksheet->querySubObject("Cells(int,int)", 1, 3*i+2);
            cell->dynamicCall("SetValue(const QString&)", str);
        }//将每个通道的标题输入到excel的第一排

        if(w->channel1->on_off)//由于子线程不能访问UI文件数据,所以不能像主线程用ui->checkbox_1->ischecked()来判断通道是否打开,中只能用Widget对象中的这个值来判断通道是否打开
        {
            QVariant var=w->datatovariant(w->model1);
            int i = w->model1->rowCount();
            QString str="A3:C"+QString::number(2+i);
            QAxObject *range=worksheet->querySubObject("Range(const QString&)",str);
            range->setProperty("Value",var);
            range->querySubObject("Interior")->setProperty("Color", QColor(255, 255, 165));
        }

        workbook->dynamicCall("SaveAs(const QString&)",QDir::toNativeSeparators(strpath));//保存至fileName
        workbook->dynamicCall("Close()");//关闭工作簿
        excel->dynamicCall("Quit()");//关闭excel
        w->flagnumber=0;
        delete excel;
        excel=nullptr;
        emit savedone();
     }
    this->quit(); 
    qDebug()<<"子线程保存数据结束";
}

SaveThread::SaveThread()
{

}

void SaveThread::getaddress(Widget *p)
{
    w=p;//将主线程Widegt对象的地址赋值给子线程中的Widget对象,这样就可以访问主线程中的所有数据方法而不受线程的限制
}
Widget.h


#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "wavemodel.h"


namespace Ui {
class Widget;
}
class SaveThread;//前置声明
class Widget : public QWidget
{
    Q_OBJECT
signals:
    void preper_save(Widget *);

public:
    friend class SaveThread;//友元声明
    int flagnumber=0;//当这个值等于0时,表明未对数据进行修改,等于1时表明有对数据进行过修改
    QString shotnum="+PLS_00000";//炮号,用来做文件名
    QString fileName;

    explicit Widget(QWidget *parent = nullptr);

    QVariant datatovariant(WaveModel *);//将数据保存到QVariant中,以便于之后将数据保存为excel

    ~Widget();

private slots:

    void Flagnum_change();//判断数据是否被修改过bool savedata_excel();//保存数据到本地
    void savesucces();//保存数据成功时弹出窗口提示

private:
    Ui::Widget *ui;
    SaveThread *s_thread;//s_thread.start()写在onUDPSocketReadyRead()函数中,收到UDP信号发送信号,并启动线程

    WaveModel *model1;
    WaveModel *model2;
    WaveModel *model3;
    WaveModel *model4;
    WaveModel *model5;
    WaveModel *model6;
    WaveModel *model7;
    WaveModel *model8;

    struct channel_data
    {
        QVector<qreal> x;
        QVector<qreal> y;
        QVector<qreal> a;
        QVector<qreal> b;
        int on_off=0;//用于表示当前通道是否打开
    };
    channel_data *channel1,*channel2,*channel3,*channel4,*channel5,*channel6,*channel7,*channel8;

};

#endif // WIDGET_H
Widget.cpp


#include "widget.h"
#include "ui_widget.h"
#include <QHostInfo>
#include <QFileDialog>
#include <QAxObject>
#include <QDesktopServices>
#include "savethread.h"

#pragma execution_character_set("utf-8")
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{
    ui->setupUi(this);

    s_thread=new SaveThread();
    fileName=ui->pathEdit->text();

    this->model1=new WaveModel(this);
    this->model2=new WaveModel(this);
    this->model3=new WaveModel(this);
    this->model4=new WaveModel(this);
    this->model5=new WaveModel(this);
    this->model6=new WaveModel(this);
    this->model7=new WaveModel(this);
    this->model8=new WaveModel(this);
    //初始化数据模型
    channel1=new channel_data;
    channel2=new channel_data;
    channel3=new channel_data;
    channel4=new channel_data;
    channel5=new channel_data;
    channel6=new channel_data;
    channel7=new channel_data;
    channel8=new channel_data;
    //初始化与八个通道数据相关的结构体
   connect(this,SIGNAL(preper_save(Widget *)),s_thread,SLOT(getaddress(Widget *)));//传递主线程指针地址
   connect(ui->savebtn,SIGNAL(clicked()),this,SLOT(savedata_excel()));//点击按钮之后保存数据
   connect(s_thread,SIGNAL(savedone()),this,SLOT(savesucces()));
}

bool Widget::savedata_excel()
{
    qDebug()<<"这是主线程的保存数据";
    QString fileName = QFileDialog::getSaveFileName(this,"保存",
                        QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),"Excel 文件(*.xls *.xlsx)");
//    QString fileName=ui->pathEdit->text();
//    fileName.append(shotnum);
//    qDebug()<<"fileName是"<<fileName;
    if(fileName!="")
    {
        //QMessageBox::information(this,tr("提示"),tr("正在保存数据,请稍等"));
        QAxObject *excel = new QAxObject;
        excel->setControl("Excel.APPlication");

        excel->dynamicCall("SetVisible (bool Visible)","false");//不显示窗体
        excel->setProperty("DisplayAlerts", false);//不显示任何警告信息。如果为true那么在关闭是会出现类似“文件已修改,是否保存”的提示
        QAxObject *workbooks = excel->querySubObject("WorkBooks");//获取工作簿集合
        workbooks->dynamicCall("Add");//新建一个工作簿
        QAxObject *workbook = excel->querySubObject("ActiveWorkBook");//获取当前工作簿
        QAxObject *worksheet = workbook->querySubObject("Worksheets(int)", 1);

        QAxObject *cell;
        for(int i = 0; i < 8; i++)
        {
            QString str="通道"+QString::number(i+1);
            cell=worksheet->querySubObject("Cells(int,int)", 1, 3*i+2);
            cell->dynamicCall("SetValue(const QString&)", str);
        }//将每个通道的标题输入到excel的第一排

        if(ui->checkBox_1->isChecked())
        {
            QVariant var=datatovariant(model1);
            int i = model1->rowCount();
            QString str="A3:C"+QString::number(2+i);
            QAxObject *range=worksheet->querySubObject("Range(const QString&)",str);
            range->setProperty("Value",var);
            range->querySubObject("Interior")->setProperty("Color", QColor(255, 255, 165));
        }

        workbook->dynamicCall("SaveAs(const QString&)",QDir::toNativeSeparators(fileName));//保存至fileName
        workbook->dynamicCall("Close()");//关闭工作簿
        excel->dynamicCall("Quit()");//关闭excel
        flagnumber=0;
        delete excel;
        excel=nullptr;
        savesucces();//弹出数据保存成功的消息框
        return true;
    }
    else
        return false;
}

QVariant Widget::datatovariant(WaveModel *model)
{
    QList<QList<QVariant> > datas;
    QVariantList vars;
    QModelIndex index;
    int rownum,colnum,num;//模型行数列数以及模型的某个位置的值
    rownum=model->rowCount();
    colnum=model->columnCount();
    for(int i=0;i<rownum;i++)
    {
        QList<QVariant> row;
        for(int j=0; j<colnum; j++)
        {
            index=model->index(i,j,QModelIndex());
            num=model->data(index,Qt::DisplayRole).toInt();
            row.append(num);
        }
        datas.append(row);
    }//通过循环将model的数据存入到QList<QList<QVariant> > datas,然后再转换为QVariant

    for(int i=0 ; i<rownum; i++)
    {
        vars.append(QVariant(datas[i]));
    }

    return QVariant(vars);
}

void Widget::savesucces()
{
    QMessageBox::information(this,tr("提示"),tr("数据已保存"));
}

 

  我们可以注意到savedata_excel()函数与run()函数执行的是一样的功能,但是补分代码确不一样,这是因为主线程中的数据可以直接访问,而子线程想要访问主线程数据则必须通过w->来进行访问和调用。

总结如下:要使得子线程能访问主线程的数据,而且是GUI主线程的数据,第一步需要将子线程声明为主线程的友元类,第二步是将主线程类对象的地址通过信号槽传递给子线程中创建的对象,就大功告成!

补充:包含的顺序不能倒过来,只能在子线程中包含头文件,头文件中写子线程的前置申明

否则会报错 member access into incompltet type

推荐阅读