Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QProgressBar and ActiveX



  • With Qt Creator, I have inserted a progress bar using the QProgressBar object. I would like the progress bar displaying the busy mode while doing some operations in Excel ( called via ActiveX)

    If I am not wrong QAxObject will start another thread so the GUI should not hang out during the ActiveX call/operations. Could you tell me what I did wrong ?

    Here is my code

    // progressBar busy mode 
    ui->progressBar->setMinimum(0);
    ui->progressBar->setMaximum(0);
    ui->progressBar->show();
    
    // Kick Excel using ActiveX
    CoInitialize(0);
    QAxObject excel( "Excel.Application", 0 );
    
    //Some Excel operations... during few seconds
    
    // Quit Excel
    excel.dynamicCall("Quit()");
    
    // Revert to normal mode
    ui->progressBar->setMaximum(100);


  • Hi, while QAxObject can do many fancy things, starting another thread outside of the GUI is not of them :-(

    But if you add some QThread/worker code and move the QAxObject stuff in there, it will work nicely with the GUI.
    Here's an example that updates a progress bar: create an empty vanilla Widget app, add axcontainer to the QT line in the .pro file, then change mainwindow.h to this:

    #include <QMainWindow>
    #include "windows.h"    // for the CoInitialize() call
    #include "QAxObject"
    #include <QThread>
    
    namespace Ui { class MainWindow; }
    
    class ExcelWorker : public QObject
    {
        Q_OBJECT
    
    public slots:
        void doWork(int n)
        {
            CoInitialize(0);
            auto excel = new QAxObject("Excel.Application",0);
            if (nullptr == excel)
                return;
    
            excel->setProperty("Visible",true);
            auto workbooks = excel->querySubObject("Workbooks");
            auto workbook  = workbooks->querySubObject("Add");
            auto sheets    = workbook->querySubObject("Worksheets");
            auto sheet     = sheets->querySubObject("Item(int)",1);
    
            for (int i = 0; (i < n); ++i)
            {
                sheet->querySubObject("Cells(int,int)",i % 10 + 1,i / 10 + 1)->setProperty("Value",i);
                QThread::msleep(100);  // simulate some heavy munging
    
                emit oneReady(i);
            }
        }
    
    signals:
        void oneReady(int i);
    };
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
        QThread excelThread;
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private slots:
        void oneReadyFromExcel(int i);
        void on_pushButton_clicked();
    
    signals:
        void doExcelWork(int n);
    
    private:
        Ui::MainWindow *ui;
    };
    

    and change the mainwindow.cpp to this:

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "qtimer.h"
    
    MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        auto ew = new ExcelWorker;
        ew->moveToThread(&excelThread);
        connect(this,&MainWindow::doExcelWork,ew,&ExcelWorker::doWork);
        connect(ew,&ExcelWorker::oneReady,this,&MainWindow::oneReadyFromExcel);
    
        excelThread.start();
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::on_pushButton_clicked()
    {
        emit doExcelWork(100);
    }
    
    void MainWindow::oneReadyFromExcel(int i)
    {
        ui->progressBar->setValue(i);
    }
    

    Finally add one progress bar and a push button and connect the clicked() signal from the push button to starting Excel and you're good to go.

    Edit: I just tested it, it runs ok but crashes when exiting the app, the example is missing the cleanup of the worker thread and quitting Excel, but I think you'll get the idea anyhow..



  • Hi, while QAxObject can do many fancy things, starting another thread outside of the GUI is not of them :-(

    But if you add some QThread/worker code and move the QAxObject stuff in there, it will work nicely with the GUI.
    Here's an example that updates a progress bar: create an empty vanilla Widget app, add axcontainer to the QT line in the .pro file, then change mainwindow.h to this:

    #include <QMainWindow>
    #include "windows.h"    // for the CoInitialize() call
    #include "QAxObject"
    #include <QThread>
    
    namespace Ui { class MainWindow; }
    
    class ExcelWorker : public QObject
    {
        Q_OBJECT
    
    public slots:
        void doWork(int n)
        {
            CoInitialize(0);
            auto excel = new QAxObject("Excel.Application",0);
            if (nullptr == excel)
                return;
    
            excel->setProperty("Visible",true);
            auto workbooks = excel->querySubObject("Workbooks");
            auto workbook  = workbooks->querySubObject("Add");
            auto sheets    = workbook->querySubObject("Worksheets");
            auto sheet     = sheets->querySubObject("Item(int)",1);
    
            for (int i = 0; (i < n); ++i)
            {
                sheet->querySubObject("Cells(int,int)",i % 10 + 1,i / 10 + 1)->setProperty("Value",i);
                QThread::msleep(100);  // simulate some heavy munging
    
                emit oneReady(i);
            }
        }
    
    signals:
        void oneReady(int i);
    };
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
        QThread excelThread;
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private slots:
        void oneReadyFromExcel(int i);
        void on_pushButton_clicked();
    
    signals:
        void doExcelWork(int n);
    
    private:
        Ui::MainWindow *ui;
    };
    

    and change the mainwindow.cpp to this:

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "qtimer.h"
    
    MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        auto ew = new ExcelWorker;
        ew->moveToThread(&excelThread);
        connect(this,&MainWindow::doExcelWork,ew,&ExcelWorker::doWork);
        connect(ew,&ExcelWorker::oneReady,this,&MainWindow::oneReadyFromExcel);
    
        excelThread.start();
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::on_pushButton_clicked()
    {
        emit doExcelWork(100);
    }
    
    void MainWindow::oneReadyFromExcel(int i)
    {
        ui->progressBar->setValue(i);
    }
    

    Finally add one progress bar and a push button and connect the clicked() signal from the push button to starting Excel and you're good to go.

    Edit: I just tested it, it runs ok but crashes when exiting the app, the example is missing the cleanup of the worker thread and quitting Excel, but I think you'll get the idea anyhow..



  • @hskoglund Thank you very much for the explanation and the code. I have not tested fully your code yet but now I understand why my code did not work as I expect.


Log in to reply