QT - 主窗口不会更新,除非它已关闭

Sal*_*din 1 c++ user-interface qt

我试图通过updateGUI每隔500毫秒调用一个线程中的函数来更新主窗口.除非我关闭窗口,否则会显示窗口但不会使用新值更新.当我这样做时,会打开一个带有新值的新窗口.我发现了这个问题,但它没有回答我的问题.我知道(如qt文档中所述)

QApplication::exec进入主事件循环并等待直到 exit()被调用.

我尝试使用,processEvents()但主窗口反复打开和关闭,非常快,我甚至看不到它.这是我的代码:

float distanceToObject;
bool objectDetected;
Modes currentMode;

void timerStart(std::function<void(void)> func, unsigned int interval)
{
    std::thread([func, interval]()
    {
        while (true)
        {
            auto x = std::chrono::steady_clock::now() + std::chrono::milliseconds(interval);
            func();
            std::this_thread::sleep_until(x);
        }
    }).detach();
}

int updateGUI(void)
{
    int argc = 0;
    char **argv = NULL;

    QApplication a(argc, argv);
    MainWindow w;
    // Set text of a label
    w.setDistance(QString::number(distanceToObject));
    // Also update objectDetected and currentMode values
    w.show();
    //a.processEvents();
    return a.exec();
}

void sendMsg(void)
{
    // Send heartbeat signal to another device
}

void receiveMsg(void)
{
    // Read messages from the other device and update the variables
    // These two values change continuously
    objectDetected = true;
    distanceToObject = 5.4;
}

void decide(void)   
{
    // The core function of the program. Takes relatively long time
    // Run a decision-making algorithm which makes decisions based on the values received from the other device. 
    // Update some variables according to the made decisions
    currentMode = Auto;
    // Execute functions according to the made decisions. 
    setMode(currentMode);
}

int main(void)
{
    timerStart(updateGUI, 500);
    timerStart(sendMsg, 1000);
    timerStart(receiveMsg, 10); 
    timerStart(decide, 500);
}
Run Code Online (Sandbox Code Playgroud)

如何正确更新变量值的主窗口?

nh_*_*nh_ 7

你的线程不更新MainWindow,但它确实创造了一个全新的QApplication,并MainWindow在每个迭代上.你的线程应该卡在里面,QApplication::exec直到你退出应用程序(例如关闭窗口).只有这样你的线程循环才能进一步发展.

通常,在从主线程外部进行更新时必须非常小心,因为通常必须在主线程内执行GUI操作.

考虑使用QThread,它已经有自己的事件循环,您可以使用它来使用相应的插槽通知/更新您的窗口.

如果没有关于您实际想要实现的目标的更多详细信息,则无法为您提供进一步的指导.我,至少,建议您在主线程中创建QApplicationMainWindow(例如main).那么这取决于你想要"更新"的内容.如果您需要处理某些数据,那么您可以在第二个线程中执行此操作,并MainWindow使用信号槽将结果发送到您的实例.如果你需要绘制到窗口,那么这必须直接在主线程中完成,或者你可能会找到一种方法QImage从线程内部渲染到一个单独的缓冲区(即),然后将此缓冲区发送到主线程将其绘制到窗口中.


我试着勾画出这样的事情.但请注意,这既不完整也不可编译,而只是一个大纲.

首先,你有你的MainWindow并添加一个signal,通知所有观察者开始做他们的工作(稍后会变得清晰).此外,您添加的slots是,只要您的某个值发生更改,就会调用它.那些slots在主线程中运行(并且是其成员MainWindow),因此可以更新窗口,但是他们需要:

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    // constructors and stuff

    void startWorking()
    {
        emit startWorkers();   
    }

public slots:
    void onModeChanged(Modes m)
    {
        // update your window with new mode
    }

    void onDistanceChanged(float distance)
    {
        // update your window with new distance
    }

signals:
    void startWorkers();
};
Run Code Online (Sandbox Code Playgroud)

接下来,您构建了一个Worker类,它封装了您想要执行的所有"后台工作"(基本上是您的线程在原始代码中执行的操作):

class Worker : public QObject
{
    Q_OBJECT
public:
    // constructors and stuff

public slots:
    void doWork()
    {
        while(!done)
        {
            // do stuff ...
            Modes m = // change mode
            emit modeModified(m);
            // do stuff ...
            float distance = // compute distance
            emit distanceModified(distance);
            // do stuff ...
        }
    }

signals:
    void modeModified(Modes m);
    void distanceModified(float distance);
};
Run Code Online (Sandbox Code Playgroud)

注意,Worker必须继承QObject并且您的doWork方法必须是a public slot.此外,您可以signal为您希望了解的每个值添加一个MainWindow.不需要实现它们,因为它是由Qt MOC(元对象编译器)生成的.每当其中一个值发生变化时,只需emit对应signal并传递新值即可.

最后,你把所有东西放在一起:

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    MainWindow window;

    // create a worker object
    Worker* worker = new Worker;

    // connect signals and slots between worker and main window
    QObject::connect(worker, &Worker::modeModified, 
        &window, &MainWindow::onModeChanged);
    QObject::connect(worker, &Worker::distanceModified, 
        &window, &MainWindow::onDistanceChanged);
    QObject::connect(&window, &MainWindow::startWorkers, 
        worker, &Worker::doWork);

    // create a new thread
    QThread* thread = new QThread;
    // send worker to work inside this new thread
    worker->moveToThread(thread);
    thread->start();

    // show window and start doing work    
    window.show();
    window.startWorking();

    // start main loop
    int result = app.exec();    

    // join worker thread and perform cleanup
    return result;
}
Run Code Online (Sandbox Code Playgroud)

好吧,让我们来看看吧.首先,在主线程中创建QApplication和创建MainWindow.接下来,创建一个Worker对象的实例(可以在这里创建多个).然后你connectworker槽的信号,window反之亦然.建立这些连接后,无论何时emit发出信号,Qt都会调用连接的插槽(并传输传递的值).请注意,此连接跨线程边界.每当从与接收对象的线程不同的线程发出信号时,Qt将发送一条消息,该消息在接收对象的线程中处理.

然后你告诉Qt你希望你worker的生活在另一个线程中使用QObject::moveToThread.请参阅此处以获取有关如何正确使用QThread其中的对象的详细说明.

其余的很简单.show你的window并开始处理.这里有不同的方法.我只是调用startWorking此方法,然后emitS中的startWorkers信号,这是连接workerdoWork方法,这样doWork将开始执行这一信号被其他线程收到后.

然后调用QApplication::exec哪个运行主线程的事件循环,其中所有这些信号都由Qt处理.关闭应用程序后(例如,通过调用quit或关闭主窗口),该exec方法将返回,您将返回main.请注意,您需要正确关闭线程(例如,通过发送停止while循环的附加信号)并加入它.您还应该删除所有已分配的对象(worker,thread).为了简化代码示例,我在此省略了这一点.


回答你的问题

我有很多函数,例如updateClips和mavReceive,它们应该定期调用并相互独立运行.我应该为每个函数创建一个不同的Worker类,因为每个函数都有不同的信号,并且每个函数都有一个QThread对象,对吧?我不再需要startTimer()了吗?如果是,我如何控制每个函数的调用间隔(以前在startTimer()中完成

来自评论:

答案很大程度上取决于"应该定期调用"的具体含义.谁应该打电话给他们?用户?或者他们应该定期执行?

因此原则上,您可以在一个线程中拥有多个工作线程.但是,如果它们应该一直工作(在while循环中旋转)它没有意义,因为一个正在运行而所有其他都被阻止.在这种情况下,每个工作者都有一个线程.

如果我理解正确,您有兴趣定期更新某些内容(例如每500毫秒).在那种情况下,我强烈建议使用QTimer.您可以设置间隔然后启动它.定时器将然后定期emittimeout信号,它可以连接到任何功能(更精确地slot)你想已执行.

更新版本Worker可能如下所示:

class Worker : public QObject
{
    Q_OBJECT
public:
    Worker()
    {
        QObject::connect(&modeTimer_, &QTimer::timeout,
            this, &Worker::onModeTimerTimeout);
        QObject::connect(&distanceTimer_, &QTimer::timeout,
            this, &Worker::onDistanceTimerTimeout);

        modeTimer_.start(500); // emit timeout() every 500ms
        distanceTimer_.start(100); // emit timeout() every 100ms
    }

public slots:    
    void onModeTimerTimeout()
    {
        // recompute mode
        Modes m = // ...
        emit modeModified(m);
    }

    void onDistanceTimerTimeout()
    {
        // recompute distance
        float distance = // ...
        emit distanceModified(distance);
    }

signals:
    void modeModified(Modes m);
    void distanceModified(float distance);

private:
    QTimer modeTimer_;
    QTimer distanceTimer_;
};
Run Code Online (Sandbox Code Playgroud)

请注意,在构造函数中建立的连接.只要其中一个定时器超时,slot就会调用connected .然后,该槽可以计算它需要的任何内容,然后MainWindow使用与signal之前相同的结果将结果发送回主线程中.

因此,如您所见,您可以在一个Worker(因此,一个线程)内有多个定时器/重新计算/更新信号.但是,实现的关键点是计算需要多长时间.如果它们花费很长时间(例如几乎与间隔一样长),那么您应该考虑使用多个线程来加速计算(意味着:在每个线程中执行一次计算).当我慢慢地想要更清楚地了解您想要实现的目标时,我想知道是否只是关于这些定期更新,您在您的问题中误用了"线程".如果确实如此,那么你根本不需要那个线程Worker.然后只需将定时器添加到您的MainWindow,并将其timeout信号直接连接到相应slotMainWindow.