GNU/Linux >> Tutoriels Linux >  >> Linux

Modifier l'interface graphique Qt à partir du thread de travail en arrière-plan

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 :

  1. On initialise la classe
  2. 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
  3. 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
  4. 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
  5. 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.
  6. 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


Linux
  1. Modifier l'arrière-plan de mon bureau dans Ubuntu 20.04 - Étapes pour le faire ?

  2. La boucle ignore le changement de variable d'un sous-shell en arrière-plan ?

  3. Modifier un courrier entrant de Text/plain à Text/html ?

  4. Comment modifier l'image d'arrière-plan par défaut du système ?

  5. Protégez votre code Java contre l'ingénierie inverse

Démarrer l'interface graphique à partir de la ligne de commande sur Ubuntu 22.04 Jammy Jellyfish

Connectez-vous en tant que root depuis l'interface graphique sur Fedora 16 | Activer la connexion root dans Fedora16

Comment vérifier si l'interface graphique est installée sous Linux à partir de la ligne de commande

Comment supprimer une interface graphique inutile d'un serveur Red Hat Enterprise Linux

2 façons de mettre à niveau d'Ubuntu 18.04 vers 18.10 (interface graphique et terminal)

comment modifier /etc/hosts à partir de scripts shell ?