stamaimer
stamaimer

Reputation: 6475

redirect qDebug to QTextEdit

I want to use qInstallMessageHandler(handler) to redirect the qDebug to QTextEdit.

I define a handler function in a class:

void Spider::redirect(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
    console->append(msg);
}

and call qInstallMessageHandler(redirect) in the constructor of the class (Spider).

But, when i compile this program, i got a error:

cannot convert 'Spider::redirect' from type 'void (Spider::)(QtMsgType, const QMessageLogContext&, const QString&)' to type 'QtMessageHandler {aka void (*)(QtMsgType, const QMessageLogContext&, const QString&)}'

If i define the handler function in global, it's ok.

I can't figure out the difference between these two behaviors.

Upvotes: 5

Views: 6118

Answers (3)

student
student

Reputation: 1

Addition to phyatts answer. To make the thing thread-safe you can also create something of the following. That will resolve the issue for any thread of that application that may want to send its debug stream.

namespace Mainwindow_ {
class Emitter : public QObject 
{
    Q_OBJECT
    public:
    Emitter(){};
    ~Emitter(){};
    signals:    
    void append_log(QString msg);
};
}

Similar to the s_textEdit create a static object of the above class. In the header:

static QTextEdit * s_text_edit;
static Mainwindow_::Emitter s_emitter;

In the .cpp:

QTextEdit * MainWindow::s_text_edit = 0;
Mainwindow_::Emitter MainWindow::s_emitter;

in its constructor:

s_text_edit = new QTextEdit("Starting .. ");
connect(&s_emitter, SIGNAL(append_log(QString)), s_text_edit, SLOT(append(QString)));

You can call that append_log from inside the myMessageOutput with:

emit MainWindow::s_emitter.append_log(msg);

Please excuse the naming differences. I tested this in Qt version 5.7 and 5.9. If there is any caveats please let me know.

Upvotes: 0

phyatt
phyatt

Reputation: 19112

I really like having this debugging ability around. I've done it a few times on the last couple projects I've worked on. Here are the relevant code snippets.

in mainwindow.h, inside your MainWindow class, under public

static QTextEdit * s_textEdit;

in mainwindow.cpp, outside of any function

QTextEdit * MainWindow::s_textEdit = 0;

in MainWindow constructor

s_textEdit = new QTextEdit;
// be sure to add the text edit into the GUI somewhere, 
// like in a layout or on a tab widget, or in a dock widget

in main.cpp, above the main()

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    if(MainWindow::s_textEdit == 0)
    {
        QByteArray localMsg = msg.toLocal8Bit();
        switch (type) {
        case QtDebugMsg:
            fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
            break;
        case QtWarningMsg:
            fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
            break;
        case QtCriticalMsg:
            fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
            break;
        case QtFatalMsg:
            fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
            abort();
        }
    }
    else
    {
        switch (type) {
        case QtDebugMsg:
        case QtWarningMsg:
        case QtCriticalMsg:
            // redundant check, could be removed, or the 
            // upper if statement could be removed
            if(MainWindow::s_textEdit != 0)
                MainWindow::s_textEdit->append(msg);
            break;
        case QtFatalMsg:
            abort();
        }
    }
}

inside main() in main.cpp, before initializing QApplication instance.

qInstallMessageHandler(myMessageOutput);

Note: This works wonderfully for any single threaded application. Once you start using qDebug() outside your GUI thread, you will crash. You then need to create a QueuedConnection from any threaded function (anything not running on your GUI thread), to connect to your instance of MainWindow::s_textEdit, like so:

QObject::connect(otherThread, SIGNAL(debug(QString)),
                 s_textEdit, SLOT(append(QString)), Qt::QueuedConnection);

If you end up using QDockWidgets and making use of the QMenu, there are some additional cool things that you can do. The end result it a very user-friendly, easy-to-manage console window.

QMenu * menu;
menu = this->menuBar()->addMenu("About");
menu->setObjectName(menu->title());

// later on...

QDockWidget *dock;
dock = new QDockWidget("Console", this);
dock->setObjectName(dock->windowTitle());
dock->setWidget(s_textEdit);
s_textEdit->setReadOnly(true);
this->addDockWidget(Qt::RightDockWidgetArea, dock);
this->findChild<QMenu*>("About")->addAction(dock->toggleViewAction());

Hope that helps.

Upvotes: 12

DmitryARN
DmitryARN

Reputation: 648

Non-static class method and a global function have different signatures. You cannot use a non-static method as a function.

Upvotes: 1

Related Questions