Le mécanisme est donc que vous ne pouvez pas modifier les widgets depuis l'intérieur d'un thread, sinon l'application plantera avec des erreurs telles que :
QObject::connect: Cannot queue arguments of type 'QTextBlock'
(Make sure 'QTextBlock' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Segmentation fault
Pour contourner ce problème, vous devez encapsuler le travail fileté dans une classe, comme :
class RunThread:public QThread{
Q_OBJECT
public:
void run();
signals:
void resultReady(QString Input);
};
Où run() contient tout le travail que vous voulez faire.
Dans votre classe parent, vous aurez une fonction d'appel générant des données et une fonction de mise à jour du widget QT :
class DevTab:public QWidget{
public:
void ThreadedRunCommand();
void DisplayData(QString Input);
...
}
Ensuite, pour appeler le fil, vous connecterez des slots, ceci
void DevTab::ThreadedRunCommand(){
RunThread *workerThread = new RunThread();
connect(workerThread, &RunThread::resultReady, this, &DevTab::UpdateScreen);
connect(workerThread, &RunThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
La fonction de connexion prend 4 paramètres, le paramètre 1 est la classe de cause, le paramètre 2 est le signal dans cette classe. Le paramètre 3 est la classe de la fonction de rappel, le paramètre 4 est la fonction de rappel au sein de la classe.
Ensuite, vous auriez une fonction dans votre thread enfant pour générer des données :
void RunThread::run(){
QString Output="Hello world";
while(1){
emit resultReady(Output);
sleep(5);
}
}
Ensuite, vous auriez un rappel dans votre fonction parent pour mettre à jour le widget :
void DevTab::UpdateScreen(QString Input){
DevTab::OutputLogs->append(Input);
}
Ensuite, lorsque vous l'exécuterez, le widget du parent sera mis à jour chaque fois que la macro d'émission sera appelée dans le thread. Si les fonctions de connexion sont correctement configurées, il prendra automatiquement le paramètre émis et le stockera dans le paramètre d'entrée de votre fonction de rappel.
Comment cela fonctionne :
- On initialise la classe
- Nous configurons les emplacements pour gérer ce qui se passe avec les finitions de thread et ce qu'il faut faire avec le "retourné" alias
emit
données car nous ne pouvons pas renvoyer les données d'un fil de la manière habituelle - nous exécutons ensuite le thread avec un
->start()
call (qui est codé en dur dans QThread), et QT recherche le nom codé en dur.run()
fonction membre dans la classe - Chaque fois que le
emit
La macro resultReady est appelée dans le thread enfant, elle stocke les données QString dans une zone de données partagée coincée dans les limbes entre les threads - QT détecte que resultReady s'est déclenché et signale à votre fonction, UpdateScreen(QString ) d'accepter la QString émise par run() comme paramètre de fonction réel dans le thread parent.
- Ceci se répète chaque fois que le mot-clé d'émission est déclenché.
Essentiellement le connect()
les fonctions sont une interface entre les threads enfant et parent afin que les données puissent aller et venir.
Remarque : resultReady() n'a pas besoin d'être défini. Considérez-le comme une macro existant dans les composants internes de QT.
La chose importante à propos de Qt est que vous devez fonctionne avec Qt GUI uniquement à partir du thread GUI, c'est-à-dire le thread principal.
C'est pourquoi la bonne façon de procéder est de notifier fil principal du travailleur, et le code dans le fil principal mettra à jour la zone de texte, la barre de progression ou autre chose.
La meilleure façon de le faire, je pense, est d'utiliser QThread au lieu de thread posix, et d'utiliser Qt signaux pour communiquer entre les threads. Ce sera votre travailleur, un remplaçant de thread_func
:
class WorkerThread : public QThread {
void run() {
while(1) {
// ... hard work
// Now want to notify main thread:
emit progressChanged("Some info");
}
}
// Define signal:
signals:
void progressChanged(QString info);
};
Dans votre widget, définissez un slot avec le même prototype que le signal en .h :
class MyWidget : public QWidget {
// Your gui code
// Define slot:
public slots:
void onProgressChanged(QString info);
};
Dans .cpp, implémentez cette fonction :
void MyWidget::onProgressChanged(QString info) {
// Processing code
textBox->setText("Latest info: " + info);
}
Maintenant, à l'endroit où vous souhaitez générer un fil (sur un clic de bouton) :
void MyWidget::startWorkInAThread() {
// Create an instance of your woker
WorkerThread *workerThread = new WorkerThread;
// Connect our signal and slot
connect(workerThread, SIGNAL(progressChanged(QString)),
SLOT(onProgressChanged(QString)));
// Setup callback for cleanup when it finishes
connect(workerThread, SIGNAL(finished()),
workerThread, SLOT(deleteLater()));
// Run, Forest, run!
workerThread->start(); // This invokes WorkerThread::run in a new thread
}
Après avoir connecté le signal et le slot, émettre le slot avec emit progressChanged(...)
dans le thread de travail enverra un message au thread principal et le thread principal appellera le slot qui est connecté à ce signal, onProgressChanged
ici.
P.s. Je n'ai pas encore testé le code, alors n'hésitez pas à suggérer une modification si je me trompe quelque part