GNU/Linux >> Tutoriales Linux >  >> Linux

Modificar Qt GUI desde el subproceso de trabajo en segundo plano

Entonces, el mecanismo es que no puede modificar los widgets desde dentro de un hilo; de lo contrario, la aplicación se bloqueará con errores como:

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

Para evitar esto, debe encapsular el trabajo subproceso en una clase, como:

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

 signals:
  void resultReady(QString Input);
};

Donde run() contiene todo el trabajo que quieres hacer.

En su clase principal, tendrá una función de llamada que genera datos y una función de actualización de widgets QT:

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

Luego, para llamar al hilo, conectará algunas ranuras, esto

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

La función de conexión toma 4 parámetros, el parámetro 1 es la clase de causa, el parámetro 2 es la señal dentro de esa clase. El parámetro 3 es una clase de función de devolución de llamada, el parámetro 4 es una función de devolución de llamada dentro de la clase.

Entonces tendría una función en su subproceso secundario para generar datos:

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

Entonces tendría una devolución de llamada en su función principal para actualizar el widget:

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

Luego, cuando lo ejecute, el widget en el padre se actualizará cada vez que se llame a la macro de emisión en el hilo. Si las funciones de conexión están configuradas correctamente, tomará automáticamente el parámetro emitido y lo guardará en el parámetro de entrada de su función de devolución de llamada.

Cómo funciona esto:

  1. Inicializamos la clase
  2. Configuramos las ranuras para manejar lo que sucede con las terminaciones de subprocesos y qué hacer con el emit "devuelto". ted datos porque no podemos devolver datos de un hilo de la manera habitual
  3. luego ejecutamos el hilo con un ->start() call (que está codificado en QThread), y QT busca el nombre codificado .run() función miembro en la clase
  4. Cada vez que el emit Se llama a la macro resultReady en el subproceso secundario, se guardan los datos de QString en un área de datos compartida atascada en el limbo entre subprocesos
  5. QT detecta que resultReady se ha activado y señala su función, UpdateScreen(QString) para aceptar el QString emitido desde run() como un parámetro de función real en el subproceso principal.
  6. Esto se repite cada vez que se activa la palabra clave emit.

Esencialmente el connect() Las funciones son una interfaz entre los subprocesos principal y secundario para que los datos puedan viajar de un lado a otro.

Nota: resultReady() no necesita ser definido. Piense en ello como una macro que existe dentro de las funciones internas de QT.


Lo importante de Qt es que debes trabaje con Qt GUI solo desde el subproceso de GUI, que es el subproceso principal.

Por eso, la forma correcta de hacerlo es notificar hilo principal del trabajador, y el código en el hilo principal en realidad actualizará el cuadro de texto, la barra de progreso o algo más.

Creo que la mejor manera de hacer esto es usar QThread en lugar de posix thread, y usar Qt signals para la comunicación entre hilos. Este será tu trabajador, un sustituto 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);
};

En su widget, defina una ranura con el mismo prototipo que la señal en .h:

class MyWidget : public QWidget {
    // Your gui code

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

En .cpp implemente esta función:

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

Ahora, en ese lugar donde desea generar un hilo (al hacer clic en el botón):

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
}

Después de conectar la señal y la ranura, emitiendo la ranura con emit progressChanged(...) en el subproceso de trabajo enviará un mensaje al subproceso principal y el subproceso principal llamará a la ranura que está conectada a esa señal, onProgressChanged aquí.

PD. Todavía no he probado el código, así que siéntete libre de sugerir una edición si me equivoco en alguna parte


Linux
  1. Modificar el fondo de mi escritorio en Ubuntu 20.04 - ¿Pasos para hacer esto?

  2. ¿Modificar un correo entrante de texto/sin formato a texto/html?

  3. Debian – ¿Eliminar Gui de Debian?

  4. ¿Cómo modificar la imagen de fondo predeterminada del sistema?

  5. ¿Abrir archivo o aplicación como raíz desde la interfaz gráfica de usuario?

Inicie la GUI desde la línea de comandos en Ubuntu 22.04 Jammy Jellyfish

Inicie sesión como root desde GUI en Fedora 16 | Habilitar inicio de sesión raíz en Fedora16

Cómo verificar si la GUI está instalada en Linux desde la línea de comandos

Cómo eliminar una GUI innecesaria de un servidor Red Hat Enterprise Linux

2 formas de actualizar de Ubuntu 18.04 a 18.10 (GUI y terminal)

Ejecutar script PHP desde la línea de comando como proceso en segundo plano