因為項目需求,需要將模型中的數據保存為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