GNU/Linux >> Znalost Linux >  >> Linux

Upravte Qt GUI z pracovního vlákna na pozadí

Mechanismus tedy spočívá v tom, že nemůžete upravovat widgety zevnitř vlákna, jinak aplikace spadne s chybami jako:

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

Chcete-li to obejít, musíte zapouzdřit práci s vlákny ve třídě, například:

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

 signals:
  void resultReady(QString Input);
};

Kde run() obsahuje veškerou práci, kterou chcete udělat.

Ve své nadřazené třídě budete mít funkci volání generující data a funkci aktualizace widgetu QT:

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

Pak pro zavolání do vlákna připojíte nějaké sloty, toto

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

Funkce připojení má 4 parametry, parametr 1 je třída příčiny, parametr 2 je signál v této třídě. Parametr 3 je třída funkce zpětného volání, parametr 4 je funkce zpětného volání v rámci třídy.

Pak byste měli ve svém podřízeném vláknu funkci pro generování dat:

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

Pak byste měli ve své rodičovské funkci zpětné volání k aktualizaci widgetu:

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

Když jej pak spustíte, widget v nadřazeném prvku se aktualizuje pokaždé, když je ve vlákně zavoláno makro emit. Pokud jsou funkce připojení správně nakonfigurovány, automaticky převezme vyslaný parametr a uloží jej do vstupního parametru vaší funkce zpětného volání.

Jak to funguje:

  1. Inicializujeme třídu
  2. Nastavujeme sloty tak, aby zvládly, co se stane s dokončením vlákna a co dělat s „vráceným“ neboli emit ted data, protože nemůžeme vrátit data z vlákna běžným způsobem
  3. pak spustíme vlákno s ->start() volání (které je pevně zakódováno do QThread) a QT hledá pevně zakódovaný název .run() členská funkce ve třídě
  4. Pokaždé emit makro resultReady je voláno v podřízeném vláknu, ukládá data QString do nějaké sdílené datové oblasti uvízlé v limbu mezi vlákny
  5. QT detekuje, že se výsledek resultReady spustil, a signalizuje vaší funkci UpdateScreen(QString), aby přijala QString vysílaný z run() jako skutečný parametr funkce v nadřazeném vláknu.
  6. To se opakuje při každém spuštění klíčového slova pro vysílání.

V podstatě connect() funkce jsou rozhraním mezi podřízeným a nadřazeným vláknem, takže data mohou cestovat tam a zpět.

Poznámka: resultReady() není nutné definovat. Představte si to jako makro existující uvnitř QT internals.


Důležité u Qt je, že musíte pracovat s Qt GUI pouze z GUI vlákna, to je hlavní vlákno.

Správný způsob, jak to udělat, je proto upozornit hlavní vlákno od pracovníka a kód v hlavním vlákně ve skutečnosti aktualizuje textové pole, ukazatel průběhu nebo něco jiného.

Myslím, že nejlepší způsob, jak toho dosáhnout, je použít QThread místo vlákna posix a použít Qt signály pro komunikaci mezi vlákny. Toto bude váš pracovník, náhradník za 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);
};

Ve svém widgetu definujte slot se stejným prototypem jako signál v .h:

class MyWidget : public QWidget {
    // Your gui code

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

V .cpp implementujte tuto funkci:

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

Nyní na místě, kde chcete vytvořit vlákno (po kliknutí na tlačítko):

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
}

Po připojení signálu a slotu se vysílá slot s emit progressChanged(...) v pracovním vláknu odešle zprávu do hlavního vlákna a hlavní vlákno zavolá slot, který je připojen k tomuto signálu, onProgressChanged zde.

P.s. Kód jsem ještě netestoval, takže pokud se někde mýlím, klidně navrhněte úpravu


Linux
  1. Upravit pozadí mé plochy v Ubuntu 20.04 – jak to provést?

  2. Upravit příchozí poštu z textu/prostého na text/html?

  3. Debian – Odebrat Gui z Debianu?

  1. Jak upravit výchozí obrázek na pozadí systému?

  2. Chraňte svůj kód Java před zpětným inženýrstvím

  3. ID vlákna vs. rukojeť vlákna

  1. Přístup k místnímu vláknu z jiného vlákna

  2. Spuštění PHP skriptu z příkazového řádku jako proces na pozadí

  3. Je možné, aby proces démona (tj. pozadí) hledal stisknutí kláves na klávesnici USB?