Как обновить данные QCustomPlot в потоке?

Я хочу отображать данные в реальном времени с помощью QCustomPlot в С++. поэтому я делаю это:

в заголовочном файле:

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();

};

и в исходном файле я использую кнопку для запуска потока и... вот так:

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();

   }

  }

но эта ошибка возникает, когда я запускаю код:

"невозможно отправлять события объектам, принадлежащим другому потоку"

Как я могу это решить?


person M.Hu    schedule 26.02.2018    source источник
comment
С данным кодом мы не можем протестировать и воспроизвести вашу проблему. Однако я думаю, что вам нужно emitсигнал от thread_Displayer_fn() и подключить его к функции в вашем основном графическом интерфейсе, которая получает данные и обновляет график.   -  person apalomer    schedule 27.02.2018
comment
Нужно ли для этого метода создавать новый класс и испускать сигнал от объекта этого класса?   -  person M.Hu    schedule 27.02.2018


Ответы (2)


Вам нужно (по крайней мере, так это работает для меня) создать сигнал, который будет испускаться при каждой итерации цикла for в вашем потоке. Затем подключите этот сигнал к слоту, который будет выполнять фактическое обновление данных.

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
    }
}

Обратите внимание, что я также добавил функцию usleep после emmit. Если не поставишь то не сможешь снова нажать кнопку и остановиться, думаю это из-за скорости с которой отправляются данные.

Здесь вы можете найти весь пример.

person apalomer    schedule 27.02.2018
comment
Спасибо. после вашего комментария я сделал то же самое, что и вы, и это работает. - person M.Hu; 27.02.2018

сделайте эту функцию статической и попробуйте void QtGuiApplication::thread_Displayer_fn()

  static void QtGuiApplication::thread_Displayer_fn()

Поскольку объект, созданный из класса QtGuiApplication, уже принадлежит основному потоку, и вы пытаетесь вызвать его метод из другого потока (дочернего потока) с помощью приведенного выше оператора.

person Abhijit Pritam Dutta    schedule 26.02.2018
comment
Если я сделаю эту функцию статической, я не смогу получить доступ к нестатическим членам данных класса. Мне нужны нестатические члены в этой функции. - person M.Hu; 27.02.2018
comment
объявите структуру с указателями всех типов данных, которые вы хотите передать этому статическому методу. Назначьте адреса всех типов данных и отправьте этот объект структуры в качестве параметра этому статическому методу и проверьте. - person Abhijit Pritam Dutta; 27.02.2018