GNU/Linux >> Linux Esercitazione >  >> Linux

Modifica la GUI di Qt dal thread di lavoro in background

Quindi il meccanismo è che non puoi modificare i widget dall'interno di un thread altrimenti l'applicazione andrà in crash con errori come:

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

Per aggirare questo problema, devi incapsulare il lavoro in thread in una classe, come:

class RunThread:public QThread{
  Q_OBJECT
 public:
  void run();

 signals:
  void resultReady(QString Input);
};

Dove run() contiene tutto il lavoro che vuoi fare.

Nella tua classe genitore avrai una funzione di chiamata che genera dati e una funzione di aggiornamento del widget QT:

class DevTab:public QWidget{
public:
  void ThreadedRunCommand();
  void DisplayData(QString Input);
...
}

Quindi per chiamare nel thread collegherai alcuni slot, questo

void DevTab::ThreadedRunCommand(){
  RunThread *workerThread = new RunThread();
  connect(workerThread, &RunThread::resultReady, this, &DevTab::UpdateScreen);
  connect(workerThread, &RunThread::finished, workerThread, &QObject::deleteLater);
  workerThread->start();  
}

La funzione di connessione accetta 4 parametri, il parametro 1 è la classe di causa, il parametro 2 è il segnale all'interno di quella classe. Il parametro 3 è la classe della funzione di callback, il parametro 4 è la funzione di callback all'interno della classe.

Quindi avresti una funzione nel thread figlio per generare dati:

void RunThread::run(){
  QString Output="Hello world";
  while(1){
    emit resultReady(Output);
    sleep(5);
  }
}

Quindi avresti una richiamata nella tua funzione genitore per aggiornare il widget:

void DevTab::UpdateScreen(QString Input){
  DevTab::OutputLogs->append(Input);
}

Quindi, quando lo esegui, il widget nel genitore si aggiornerà ogni volta che la macro emit viene chiamata nel thread. Se le funzioni di connessione sono configurate correttamente, prenderà automaticamente il parametro emesso e lo nasconderà nel parametro di input della tua funzione di callback.

Come funziona:

  1. Inizializziamo la classe
  2. Impostiamo gli slot per gestire ciò che accade con la fine del thread e cosa fare con il "restituito" ovvero emit ted perché non possiamo restituire i dati da un thread nel solito modo
  3. quindi eseguiamo il thread con un ->start() call (che è hardcoded in QThread), e QT cerca il nome hardcoded .run() funzione membro nella classe
  4. Ogni volta che il emit La macro resultReady viene chiamata nel thread figlio, nasconde i dati QString in un'area dati condivisa bloccata nel limbo tra i thread
  5. QT rileva che resultReady si è attivato e segnala alla tua funzione, UpdateScreen(QString ) di accettare la QString emessa da run() come parametro di funzione effettivo nel thread padre.
  6. Ciò si ripete ogni volta che viene attivata la parola chiave emit.

Essenzialmente il connect() le funzioni sono un'interfaccia tra i thread figlio e genitore in modo che i dati possano viaggiare avanti e indietro.

Nota: resultReady() non ha bisogno di essere definito. Pensala come una macro esistente all'interno di QT.


La cosa importante di Qt è che devi funziona con Qt GUI solo dal thread della GUI, ovvero il thread principale.

Ecco perché il modo corretto per farlo è avvisare thread principale da worker e il codice nel thread principale aggiornerà effettivamente la casella di testo, la barra di avanzamento o qualcos'altro.

Il modo migliore per farlo, penso, è usare QThread invece di posix thread e usare Qt signals per la comunicazione tra i thread. Questo sarà il tuo lavoratore, un sostituto di 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);
};

Nel tuo widget, definisci uno slot con lo stesso prototipo del segnale in .h:

class MyWidget : public QWidget {
    // Your gui code

    // Define slot:
    public slots:
    void onProgressChanged(QString info);
};

In .cpp implementa questa funzione:

void MyWidget::onProgressChanged(QString info) {
    // Processing code
    textBox->setText("Latest info: " + info);
}

Ora nel punto in cui vuoi generare un thread (al clic del pulsante):

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
}

Dopo aver collegato segnale e slot, emettendo slot con emit progressChanged(...) nel thread di lavoro invierà un messaggio al thread principale e il thread principale chiamerà lo slot connesso a quel segnale, onProgressChanged qui.

Ps. Non ho ancora testato il codice, quindi sentiti libero di suggerire una modifica se sbaglio da qualche parte


Linux
  1. Il ciclo ignora la modifica della variabile da una subshell in background?

  2. Come inviare sequenze di tasti (f5) dal terminale a un programma Gui?

  3. Debian – Rimuovere Gui da Debian?

  4. Proteggi il tuo codice Java dal reverse engineering

  5. ID thread e handle del thread

Avvia la GUI dalla riga di comando su Ubuntu 22.04 Jammy Jellyfish

Come verificare se la GUI è installata in Linux dalla riga di comando

Come rimuovere una GUI non necessaria da un server Red Hat Enterprise Linux

2 modi per eseguire l'aggiornamento da Ubuntu 18.04 a 18.10 (GUI e terminale)

È sicuro eseguire il fork dall'interno di un thread?

come modificare /etc/hosts dagli script di shell?