GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Ändern Sie die Qt-GUI aus dem Hintergrund-Worker-Thread

Der Mechanismus besteht also darin, dass Sie Widgets nicht innerhalb eines Threads ändern können, da die Anwendung sonst mit Fehlern wie den folgenden abstürzt:

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

Um dies zu umgehen, müssen Sie die Thread-Arbeit in einer Klasse kapseln, wie zum Beispiel:

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

 signals:
  void resultReady(QString Input);
};

Wobei run() die gesamte Arbeit enthält, die Sie erledigen möchten.

In Ihrer übergeordneten Klasse haben Sie eine Aufruffunktion, die Daten generiert, und eine QT-Widget-Aktualisierungsfunktion:

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

Um dann in den Thread einzusteigen, verbinden Sie einige Slots, diese

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

Die Verbindungsfunktion benötigt 4 Parameter, Parameter 1 ist die Ursachenklasse, Parameter 2 ist das Signal innerhalb dieser Klasse. Parameter 3 ist die Klasse der Callback-Funktion, Parameter 4 ist die Callback-Funktion innerhalb der Klasse.

Dann hätten Sie eine Funktion in Ihrem untergeordneten Thread, um Daten zu generieren:

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

Dann hätten Sie einen Rückruf in Ihrer übergeordneten Funktion, um das Widget zu aktualisieren:

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

Wenn Sie es dann ausführen, wird das Widget im übergeordneten Element jedes Mal aktualisiert, wenn das Emit-Makro im Thread aufgerufen wird. Wenn die Connect-Funktionen richtig konfiguriert sind, nimmt sie automatisch den ausgegebenen Parameter und steckt ihn in den Eingabeparameter Ihrer Callback-Funktion.

Wie das funktioniert:

  1. Wir initialisieren die Klasse
  2. Wir richten die Slots so ein, dass sie handhaben, was mit den Fadenenden passiert und was mit dem "zurückgegebenen" aka emit zu tun ist ted data, weil wir Daten von einem Thread nicht auf die übliche Weise zurückgeben können
  3. Dann führen wir den Thread mit einem ->start() aus call (der fest in QThread codiert ist) und QT sucht nach dem fest codierten Namen .run() Mitgliedsfunktion in der Klasse
  4. Jedes Mal die emit Das resultReady-Makro wird im untergeordneten Thread aufgerufen, es speichert die QString-Daten in einem gemeinsam genutzten Datenbereich, der zwischen den Threads in der Schwebe steckt
  5. QT erkennt, dass resultReady ausgelöst wurde, und signalisiert Ihrer Funktion UpdateScreen(QString ), den von run() ausgegebenen QString als tatsächlichen Funktionsparameter im übergeordneten Thread zu akzeptieren.
  6. Dies wiederholt sich jedes Mal, wenn das Schlüsselwort emit ausgelöst wird.

Im Wesentlichen die connect() Funktionen sind eine Schnittstelle zwischen den untergeordneten und übergeordneten Threads, sodass Daten hin und her reisen können.

Hinweis: resultReady() muss nicht definiert werden. Betrachten Sie es als ein Makro, das innerhalb von QT-Interna existiert.


Wichtig bei Qt ist, dass Sie müssen Arbeite mit der Qt-GUI nur vom GUI-Thread, also dem Haupt-Thread.

Deshalb ist der richtige Weg, dies zu tun, eine Benachrichtigung Hauptthread vom Worker, und der Code im Hauptthread aktualisiert tatsächlich das Textfeld, den Fortschrittsbalken oder etwas anderes.

Der beste Weg, dies zu tun, ist meiner Meinung nach die Verwendung von QThread anstelle von Posix-Thread und die Verwendung von Qt-Signalen für die Kommunikation zwischen Threads. Dies wird Ihr Arbeiter sein, ein Ersatz für 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);
};

Definieren Sie in Ihrem Widget einen Slot mit gleichem Prototyp wie Signal in .h:

class MyWidget : public QWidget {
    // Your gui code

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

Implementieren Sie in .cpp diese Funktion:

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

Jetzt an der Stelle, an der Sie einen Thread erzeugen möchten (auf Knopfdruck):

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
}

Nachdem Sie Signal und Steckplatz verbunden haben, wird der Steckplatz mit emit progressChanged(...) ausgegeben im Worker-Thread sendet eine Nachricht an den Haupt-Thread und der Haupt-Thread ruft den Slot auf, der mit diesem Signal verbunden ist, onProgressChanged hier.

PS. Ich habe den Code noch nicht getestet, also zögern Sie nicht, eine Änderung vorzuschlagen, wenn ich irgendwo falsch liege


Linux
  1. Ändern Sie meinen Desktop-Hintergrund in Ubuntu 20.04 - Schritte dazu?

  2. Eine eingehende Mail von Text/plain zu Text/html ändern?

  3. Debian – GUI aus Debian entfernen?

  4. Wie ändere ich das Standard-Hintergrundbild des Systems?

  5. Datei oder Anwendung als Root von Gui öffnen?

Starten Sie die GUI von der Befehlszeile auf Ubuntu 22.04 Jammy Jellyfish

So überprüfen Sie, ob die GUI in Linux über die Befehlszeile installiert ist

So entfernen Sie eine nicht benötigte GUI von einem Red Hat Enterprise Linux-Server

2 Möglichkeiten zum Upgrade von Ubuntu 18.04 auf 18.10 (GUI &Terminal)

Ist es sicher, innerhalb eines Threads zu forken?

Wie ändert man /etc/hosts aus Shell-Skripten?