c++multithreadingqtqcustomplot

How to update QCustomPlot Data in thread?


I want to display real time data with help of QCustomPlot in c++ . so I do this:

in header file:

class QtGuiApplication : public QMainWindow
{
Q_OBJECT

public:
QtGuiApplication(QWidget *parent = Q_NULLPTR);


private:
Ui::QtGuiApplicationClass ui;

//plot
QCustomPlot*        plot_;
std::thread thread_Displayer_;
bool thread_run_flag_ = false;
void thread_Displayer_fn();

};

and in source file I use a push Button to start thread and ... like this:

void     QtGuiApplication::btn_Display_clicked()
{

if (thread_run_flag_)
{
    ui.btn_Dispaly->setText("Display");
    thread_run_flag_ = false;
    if (thread_Displayer_.joinable())
    {
        thread_Displayer_.join();
    }
}
else
{
    thread_run_flag_ = false;
    if (thread_Displayer_.joinable())
    {
        thread_Displayer_.join();
    }
    ui.btn_Dispaly->setText("Stop");
    thread_run_flag_ = true;
    thread_Displayer_ = std::thread(&QtGuiApplication::thread_Displayer_fn, 
      this);
}


}

void     QtGuiApplication::thread_Displayer_fn()
{
double y_max_ = 0;
double y_min_ = 0;
while (thread_run_flag_)
{
    QVector<double> x(16384), y(16384); 
    for (int i = 0; i<16384; ++i)
    {
        x[i] = i; 
        y[i] = x[i]; 
        if (y[i] > y_max_)
            y_max_ = y[i];
        if (y[i] < y_min_)
            y_min_ = y[i];
    }

    plot_->yAxis->setRange(y_min_, y_max_);
    plot_->graph(0)->setData(x, y);
    plot_->replot();
    
   }

  }

but This error occurs when I start the code:

"cannot send events to objects owned by a different thread"

How can I solve it?


Solution

  • You need to (or at least this is how it works for me) create a signal that will be emitted every iteration of the for loop in your thread. Then, connect this signal to a slot that will do the actual update of the data

    class QtGuiApplication : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit QtGuiApplication(QWidget *parent = 0);
        ~QtGuiApplication();
    private slots:
        void setPlotData(QVector<double> x,QVector<double> y);
        void pushButtonClicked();
    signals:
        void newData(QVector<double> x, QVector<double> y);
    private:
        Ui::QtGuiApplication *ui;
        QCustomPlot* plot_;
        std::thread thread_Displayer_;
        bool thread_run_flag_ = false;
        void thread_Displayer_fn();
    };
    
    double fRand(double fMin, double fMax){
        double f = (double)rand() / RAND_MAX;
        return fMin + f * (fMax - fMin);
    }
    
    QtGuiApplication::QtGuiApplication(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::QtGuiApplication)
    {
        // Set up UI
        ui->setupUi(this);
        plot_ = ui->qcustomplot;
    
        // Register data type for signals
        qRegisterMetaType<QVector<double>>("QVector<double>");
    
        // Prepare graph
        plot_->addGraph();
    
        // Connect
        connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(pushButtonClicked()));
        connect(this,SIGNAL(newData(QVector<double>,QVector<double>)),this,SLOT(setPlotData(QVector<double>,QVector<double>)));
    }
    
    QtGuiApplication::~QtGuiApplication()
    {
        delete ui;
    }
    
    void QtGuiApplication::pushButtonClicked()
    {
        if (thread_run_flag_)
        {
            ui->pushButton->setText("Display");
            thread_run_flag_ = false;
            if (thread_Displayer_.joinable())
            {
                thread_Displayer_.join();
            }
        }
        else
        {
            thread_run_flag_ = false;
            if (thread_Displayer_.joinable())
            {
                thread_Displayer_.join();
            }
            ui->pushButton->setText("Stop");
            thread_run_flag_ = true;
            thread_Displayer_ = std::thread(&QtGuiApplication::thread_Displayer_fn,
              this);
        }
    }
    
    void QtGuiApplication::setPlotData(QVector<double> x, QVector<double> y)
    {
        plot_->graph(0)->setData(x, y);
        plot_->rescaleAxes();
        plot_->replot();
    }
    
    void QtGuiApplication::thread_Displayer_fn()
    {
        while (thread_run_flag_)
        {
            QVector<double> x(ui->spinBox->value()), y(ui->spinBox->value());
            for (int i = 0; i<ui->spinBox->value(); ++i)
            {
                x[i] = i;
                y[i] = fRand(0,10);
            }
            emit newData(x,y);
            usleep(ui->doubleSpinBox->value()*1000);// convert to us
        }
    }
    

    Note that I also added a usleep function after the emmit. If you do not put it you will not be able to push the button again and stop, I think it is because of the rate at which the data is sent.

    Here you can find the whole example.