如何在线程中更新QCustomPlot数据?解决跨线程事件发送错误
Hey there, let's break down this problem and fix it step by step. That error message is Qt's strict reminder that GUI widgets (including QCustomPlot) can only be modified from the thread they were created in—which is almost always the main (UI) thread. Your worker thread is trying to update the plot directly, which violates Qt's thread-safety rules.
Here are the most reliable solutions to get your real-time data display working:
1. Use Qt's Signal-Slot Mechanism (Recommended)
This is the cleanest, most Qt-idiomatic way to handle cross-thread UI updates. The idea is to have your worker thread emit a signal with the new data, and connect that signal to a slot in your main window class that updates the QCustomPlot. Qt automatically queues cross-thread signal-slot connections, so the slot runs safely in the main thread.
Step 1: Define the Worker Class
In your header file (e.g., worker.h):
#include <QThread> class Worker : public QThread { Q_OBJECT public: explicit Worker(QObject *parent = nullptr); void stop(); // To safely terminate the thread signals: // Signal to send new data to the main thread void newDataReady(double x, double y); protected: void run() override; private: bool m_running = false; };
Step 2: Implement the Worker Thread
In worker.cpp:
#include "worker.h" #include <QRandomGenerator> // Replace with your real data source Worker::Worker(QObject *parent) : QThread(parent) {} void Worker::stop() { m_running = false; } void Worker::run() { m_running = true; double x = 0.0; while (m_running) { // Generate or fetch your real-time data here double y = QRandomGenerator::global()->generateDouble() * 100; // Emit the signal with the new data emit newDataReady(x, y); x += 0.1; msleep(100); // Simulate data acquisition delay } }
Step 3: Connect Signal to Slot in Main Window
In your main window header (e.g., mainwindow.h):
#include <QMainWindow> #include "qcustomplot.h" #include "worker.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_startButton_clicked(); void on_stopButton_clicked(); // Slot to update QCustomPlot with new data void updatePlot(double x, double y); private: Ui::MainWindow *ui; Worker *m_worker; };
In mainwindow.cpp:
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // Initialize QCustomPlot (configure axes, etc.) ui->customPlot->addGraph(); ui->customPlot->xAxis->setLabel("X"); ui->customPlot->yAxis->setLabel("Y"); ui->customPlot->xAxis->setRange(0, 10); ui->customPlot->yAxis->setRange(0, 100); // Create worker instance m_worker = new Worker(this); // Connect worker's signal to our update slot connect(m_worker, &Worker::newDataReady, this, &MainWindow::updatePlot); } MainWindow::~MainWindow() { m_worker->stop(); m_worker->wait(); delete ui; } void MainWindow::on_startButton_clicked() { if (!m_worker->isRunning()) { m_worker->start(); } } void MainWindow::on_stopButton_clicked() { m_worker->stop(); } void MainWindow::updatePlot(double x, double y) { // This slot runs in the main thread, so it's safe to modify QCustomPlot ui->customPlot->graph(0)->addData(x, y); // Auto-scroll the x-axis if needed if (x > ui->customPlot->xAxis->range().upper) { ui->customPlot->xAxis->setRange(x, 10, Qt::AlignRight); } ui->customPlot->replot(); }
2. Use QMetaObject::invokeMethod (Quick Fix for Simple Cases)
If you don't want to create a separate signal, you can use QMetaObject::invokeMethod to call the plot update function directly from the worker thread, forcing it to run in the main thread.
In your worker's run() method:
void Worker::run() { m_running = true; double x = 0.0; while (m_running) { double y = QRandomGenerator::global()->generateDouble() * 100; // Call the plot update function in the main thread QMetaObject::invokeMethod(ui->customPlot, [=]() { ui->customPlot->graph(0)->addData(x, y); if (x > ui->customPlot->xAxis->range().upper) { ui->customPlot->xAxis->setRange(x, 10, Qt::AlignRight); } ui->customPlot->replot(); }, Qt::QueuedConnection); x += 0.1; msleep(100); } }
⚠️ Note: Make sure your worker has safe access to the QCustomPlot pointer (but storing it as a member is not ideal—signal-slot is still better practice).
Key Rules to Remember
- Never modify GUI widgets directly from a non-main thread: Qt's widget system is not thread-safe for direct access.
- Don't use
moveToThreadon QCustomPlot: Moving a GUI widget to a worker thread will cause more errors, as widgets must live in the main thread. - Always clean up threads properly: Call
stop()(or set a flag) andwait()before deleting the worker thread to avoid crashes.
内容的提问来源于stack exchange,提问作者M.Hu

