Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to show data from a blocking socket in QEditText



  • I have a QDialog with a QEditText object and I want to show data (it's a log from a server) from a blocking socket, I get this data using an API call that uses a callback to get the lines from the this log, this callback basically never ends, only way to finish it is
    send a signal to force a close on it (in a command line I use ctrl+c).

    The issue I'm having is how to implement the thread and connect the slots/signals correctly.

    My main window has a menu option to just open the dialog where I will show the data, this dialog has the QEditText in the UI, I also have a button to "start capturing" the log.

    this is my header:

    namespace Ui {
    class EventMonitor;
    }
    
    class EventMonitor : public QDialog
    {
        Q_OBJECT
    
    public:
        explicit EventMonitor(QWidget *parent = nullptr);
        ~EventMonitor();
    
    private slots:
        void on_pushButton_3_clicked();
    
    private:
        Ui::EventMonitor *ui;
    };
    
    class Worker : public QObject {
        Q_OBJECT
    
    public:
        Worker();
        ~Worker();
    
    public slots:
        void process();
    
    signals:
        void finished();
        void error(QString err);
    
    private:
    };
    

    In my push button event I create the thread

    void EventMonitor::on_pushButton_3_clicked()
    {
        QThread* thread = new QThread;
        Worker* worker = new Worker();
        worker->moveToThread(thread);
        connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
        connect(thread, SIGNAL(started()), worker, SLOT(process()));
        connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
        connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
        connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
        thread->start(); 
    }
    

    then In the worker process I call the API functions

    void Worker::process()
    {   
        openSession(IP, USER, PASS , &hPocket);
        DGetLogEvents(hPocket, ReceiveLogCallback, QTextEditBOX);
        emit finished();
    }
    

    This last parameter (QTextEditBox) is a void that I thought I could use to send the QTextEdit pointer and write to it inside the callback, but found out that in a different thread I couldn't write to other thread objects :(
    The callback is simple it will just write the log line to a char *szEvent .

    
    int AAP_API ReceiveLogCallback(char *szEvent, void *pParam, BOOL bFinal)
    {  
        //pParam->setText(szEvent) -> something similar to this will never work.
        emit(szEvent);
        return 0;
    }
    

    So, I was thinking that the easiest way would be emit(szEvent) to a slot that has the QTextEdit from the UI in the other thread, but I'm not sure how to do this, since the callback is not in the same class


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Create a QString from the data your received and then use QMetaObject::invokeMethod with Qt::QueuedConnection type. That way, the slot will be executed in the right thread.



  • @SGaist

    thanks for the reply, the invoke seems like a good idea, but I'm not sure how to implement this as well.. the problem is this callback is a blocking socket that never closes, so it never leaves the callback function. How can I invoke from there if I can't access the UI object?


  • Lifetime Qt Champion

    Can't you pass a custom structure or data to that callback when you register it ?



  • yes, I can do something like this

    void Worker::process()
    {   
        QString line;
        openSession(IP, USER, PASS , &hPocket);
        DGetLogEvents(hPocket, ReceiveLogCallback, &line);
        emit finished();
    }
    

    then in the callback

    int AAP_API ReceiveLogCallback(char *szEvent, void *pParam, BOOL bFinal)
    {
    QString *line = *pParam;
    line = QString::fromLocal8Bit(szEvent);
    return 0;
    }

    but the issue is that the function will never reach emit finished(); , it gets stuck in the callback forever.. so I need to emit this value somewhere I guess? or is it possible to connect to this QString line created in the worker class to get the value in the UI thread? Can you explain a little bit if possible? I'm somewhat noobish in QT.


  • Lifetime Qt Champion

    Pass the pointer to your widget as parameter of the callback. Then in your callback you can use invokeMethod.


  • Lifetime Qt Champion

    Another possibility that will be cleaner: add a signal to your worker to emit the string and pass a pointer to your worker as parameter of your callback.

    This will keep things cleanly separated.



  • @SGaist said in How to show data from a blocking socket in QEditText:

    pass a pointer t

    thanks, I think im getting closer, but I still don't get something.

    how do I send a signal from the callback to write on the gui now?

    I got this so far:

    eventmonitor.h

    #ifndef EVENTMONITOR_H
    #define EVENTMONITOR_H
    
    #include "stdafx.h"
    #include "worker.h"
    #include <QTextEdit>
    #include <QThread>
    
    namespace Ui {
    class EventMonitor;
    }
    
    class EventMonitor : public QDialog
    {
        Q_OBJECT
    
    public:
        explicit EventMonitor(QWidget *parent = nullptr);
        ~EventMonitor();
    
    signals:
        void sendLine(QString *line);
    
    private slots:
        void on_start_logButton_clicked();    
    
    public slots:
        void receive_line(QString line);
    
    private:
        Ui::EventMonitor *ui;
        Worker       m_worker;
    };
    
    #endif // EVENTMONITOR_H
    

    eventmonitor.cpp

    #include "eventmonitor.h"
    #include "ui_eventmonitor.h"
    #include "hsm.h"
    
    EventMonitor::EventMonitor(QWidget *parent) :
        QDialog(parent),
        ui(new Ui::EventMonitor)
    {
        ui->setupUi(this);   
        connect(&m_worker, SIGNAL(sendLineToGui(QString)), this, SLOT(receive_line(QString)));
        connect(this, SIGNAL(sendLine(QString *)), &m_worker, SLOT(process(QString *)));
    }
    
    EventMonitor::~EventMonitor()
    {
        delete ui;
    }
    
    void EventMonitor::receive_line(QString logLine)
    {
        ui->textEdit->setText(logLine);
    }
    
    void EventMonitor::on_start_logButton_clicked()
    {
        QString line;
        QThread* thread = new QThread;
        Worker* worker = new Worker();
        emit sendLine(&line);
        worker->moveToThread(thread);
    
        connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
        connect(thread, SIGNAL(started()), worker, SLOT(process()));
        connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
        connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
        connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
        thread->start();
    }
    

    worker.h

    #ifndef WORKER_H
    #define WORKER_H
    
    #include <QObject>
    #include "stdafx.h"
    
    class Worker : public QObject
    {    Q_OBJECT
    
    public:
         explicit Worker(QObject * parent = 0);
        ~Worker();
    
    public slots:
        void process(QString *line);
    
    signals:
        void sendLineToGui(QString line);
        void finished();
        void error(QString err);
    
    private:
    };
    
    #endif // WORKER_H
    

    worker.cpp

    #include "worker.h"
    
    Worker::Worker(QObject *parent)
    {
    }
    
    Worker::~Worker()
    {
    }
    
    int AAP_API ReceiveLogCallback(char *szEvent, void *pParam, BOOL bFinal)
    {
        QString *line = (QString*)pParam;
        *line = QString::fromLocal8Bit(szEvent);
        //emit sendLineToGui
        return 0;
    }
    
    void Worker::process(QString *line)
    {
        HSM::HPOCKET hPocket;
        std::string sIP(CSP::GetCSPIp());
        HSM::libclient::openSession(CSP::GetCSPIp(), __INTERNAL_USER_CER_database__, __INTERNAL_USER_PASS_CER_database__ , &hPocket);
        DGetLogEvents(hPocket, ReceiveLogCallback, line);
        emit finished();
    }
    


  • thanks! managed to do it like you said before, I passed the widget pointer to the callback parameter, it feels like bad design, but it's working.


  • Lifetime Qt Champion

    That's the wrong approach. As I suggested, use invokeMethod to emit the signal.


Log in to reply