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:
- Inizializziamo la classe
- 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 - quindi eseguiamo il thread con un
->start()
call (che è hardcoded in QThread), e QT cerca il nome hardcoded.run()
funzione membro nella classe - 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 - 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.
- 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