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)
如何正确更新变量值的主窗口?
你的线程不更新MainWindow,但它确实创造了一个全新的QApplication,并MainWindow在每个迭代上.你的线程应该卡在里面,QApplication::exec直到你退出应用程序(例如关闭窗口).只有这样你的线程循环才能进一步发展.
通常,在从主线程外部进行更新时必须非常小心,因为通常必须在主线程内执行GUI操作.
考虑使用QThread,它已经有自己的事件循环,您可以使用它来使用相应的插槽通知/更新您的窗口.
如果没有关于您实际想要实现的目标的更多详细信息,则无法为您提供进一步的指导.我,至少,建议您在主线程中创建QApplication和MainWindow(例如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对象的实例(可以在这里创建多个).然后你connect的worker槽的信号,window反之亦然.建立这些连接后,无论何时emit发出信号,Qt都会调用连接的插槽(并传输传递的值).请注意,此连接跨线程边界.每当从与接收对象的线程不同的线程发出信号时,Qt将发送一条消息,该消息在接收对象的线程中处理.
然后你告诉Qt你希望你worker的生活在另一个线程中使用QObject::moveToThread.请参阅此处以获取有关如何正确使用QThread其中的对象的详细说明.
其余的很简单.show你的window并开始处理.这里有不同的方法.我只是调用startWorking此方法,然后emitS中的startWorkers信号,这是连接worker的doWork方法,这样doWork将开始执行这一信号被其他线程收到后.
然后调用QApplication::exec哪个运行主线程的事件循环,其中所有这些信号都由Qt处理.关闭应用程序后(例如,通过调用quit或关闭主窗口),该exec方法将返回,您将返回main.请注意,您需要正确关闭线程(例如,通过发送停止while循环的附加信号)并加入它.您还应该删除所有已分配的对象(worker,thread).为了简化代码示例,我在此省略了这一点.
回答你的问题
我有很多函数,例如updateClips和mavReceive,它们应该定期调用并相互独立运行.我应该为每个函数创建一个不同的Worker类,因为每个函数都有不同的信号,并且每个函数都有一个QThread对象,对吧?我不再需要startTimer()了吗?如果是,我如何控制每个函数的调用间隔(以前在startTimer()中完成
来自评论:
答案很大程度上取决于"应该定期调用"的具体含义.谁应该打电话给他们?用户?或者他们应该定期执行?
因此原则上,您可以在一个线程中拥有多个工作线程.但是,如果它们应该一直工作(在while循环中旋转)它没有意义,因为一个正在运行而所有其他都被阻止.在这种情况下,每个工作者都有一个线程.
如果我理解正确,您有兴趣定期更新某些内容(例如每500毫秒).在那种情况下,我强烈建议使用QTimer.您可以设置间隔然后启动它.定时器将然后定期emit的timeout信号,它可以连接到任何功能(更精确地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信号直接连接到相应slot的MainWindow.