Seite 1 von 1

Log ausgaben im Textfeld?

Verfasst: 21. April 2009 20:18
von CKassiopaia
Hallo zusammen!

Ich bin grad dabei ein Qt4.5-Programm unter Linux zu schreiben. Das Wichtigste vorweg: Ich bin Anfänger in Qt und auch nicht der beste in C++ aber ich lerne gerne etwas dazu.

Also mein Problem/Frage: Ich möchte Debug-Ausgaben (von mir aus auch die stdout/stderr) in einem Textfeld unter Qt ausgeben.

Ich habe hier auch schon einiges dazu gelesen, aber ich hab noch nicht wirklich verstanden wie es gehen soll.
Ich habe mich schon mit qDebug() befasst nur leider is mir die Anwendung von qInstallMsgHandler nicht richtig bekannt.

Hab mir das hier schon durchgelesen:
http://doc.trolltech.com/4.1/qtglobal.h ... MsgHandler und das Funktioniert auch soweit. Wenn ich jetzt aber darin versuche, die Ausgabe in ein Textfeld (einer fremden Klasse) mit append zu schreiben...scheiter ich.

Und bei dem hier: http://qtforum.de/forum/viewtopic.php?t ... msghandler fehlt leider die Datei mit dem Quelltext...

Habt ihr irgendwelche Links wo hierzu gute Beschreibungen sind?

Und eine andere Frage: Welches Textfeld ist hierfür wohl am besten Geeignet? TextBrowser, TextEdit, PlainTextEdit oder gibts andere alternativen?

Vielen Dank!

Verfasst: 22. April 2009 08:53
von CKassiopaia
Ich hab noch mal ein wenig herumgebastelt. Mit der gegebenen Funktion (s. Link oben) klappt die Umlenkung schon mal wunderbar.

Code: Alles auswählen

    #include <qapplication.h>
    #include <stdio.h>
    #include <stdlib.h>

    void myMessageOutput(QtMsgType type, const char *msg)
    {
        switch (type) {
        case QtDebugMsg:
            fprintf(stderr, "Debug: %s\n", msg);
            break;
        case QtWarningMsg:
            fprintf(stderr, "Warning: %s\n", msg);
            break;
        case QtCriticalMsg:
            fprintf(stderr, "Critical: %s\n", msg);
            break;
        case QtFatalMsg:
            fprintf(stderr, "Fatal: %s\n", msg);
            abort();
        }
    }

    int main(int argc, char **argv)
    {
        qInstallMsgHandler(myMessageOutput);
        QApplication app(argc, argv);
        ...
        return app.exec();
    }
Wenn ich dann:

Code: Alles auswählen

#include <QTextBrowser>
...
 void myMessageOutput(QtMsgType type, const char *msg)
    {
        static QTextBrowser *textbrowser = new QTextBrowser(); // NEU        
        switch (type) {
        case QtDebugMsg:
            fprintf(stderr, "Debug: %s\n", msg);
            textbrowser->append(msg); // NEU
            textbrowser->show();  // NEU
            break;
...
einfüge, kann ich auch die Logs in ein (separatem) Textfenster ausgeben.

Ich möchte aber die Logs in ein vorhandenes Textfenster in einer erzeugten Klasse ausgeben. Ich habe mit QDesigner ein Ui erzeugt. welches auch über die main aufgerufen wird...

Ich habe auch schon die Code-Schnipsel in die eigentliche Klasse verschoben "void Klasse::myMessageOutput(...){};" und qInstallMsgHandler(myMessageOutput) in den Konstruktor verschoben. Allerdings muss ich dann komischerweise die Funktion im header als static deklarieren und dann kann ich halt nicht mehr auf das Textfeld zugreifen... static halt ;-)

Ich hoffe ihr konntet mir folgen. Wenn ihr weitere Infos benötigt, bitte fragen!

Und besten Dank!

Verfasst: 22. April 2009 09:14
von franzf
Du kannst dir doch irgendwo eine statische Methode hinzufügen, in der du dir ein TextEdit - DAS Logger-Widget - zurückgeben lässt.
Vllt. hast du schon eine Logger-Klasse. Dann wäre doch folgendes möglich:

Code: Alles auswählen

class Logger {
  public:
    static QTextEdit* loggerWidget() {
      static QTextEdit edit;
      return &edit;
    }
};
Und halt noch die übrigen Methoden.

Wenn du es ganz fein machen willst, mach das edit als private static member, und biete neben dem loggerWidget() noch ein setLoggerWidget() an, damit man auch evtl. spezialisierte Widgets setzen kann.

Musst halt dann dieses (static) Widget in deinem MainWindow anzeigen, und in deinem MessageHandler verwenden.


Grüße
Franz

Anderer Weg

Verfasst: 22. April 2009 15:28
von CKassiopaia
Hallo,

Danke für den Hinweis. Da ich das aber auch nicht wirklich hinbekommen habe, hab ich einen anderen Weg genommen:
1.

Code: Alles auswählen

fstream logfile("File", ios::out);
cout.rdbuf(logfile.rdbuf());
Also stdout in ein Logfile geschrieben. (Wollte ich sowieso irgendwann mal haben ;-) ) Klappt auch wunderbar. Was mich nur noch stört. Das ist ja "reines c/c++" gibts da irgendwelche (besseren) wege auf Qt-Basis? Wie QTextStream?

2. Hab dann anschließend das Logfile mit QTextStream eingelesen und im QTextBrowser eingelesen. Klappt auch wunderbar. Hab allerdings das erstmal nur auf "Knopfdruck" realisiert. Später sollte das mal Synchron laufen. Habt ihr dafür Tipps?

Gibt es irgendwelche SIGNALs die gesendet werden, wenn
a) eine Datei geändert wird?
b) cout/cerr gesendet wird?
oder muss ich
c) ein Timer nehmen?

Vielen Dank!

Verfasst: 22. April 2009 16:49
von franzf
"Funktioniert nicht" ist immer eine gute Fehlerangabe...
Ich hab trotzdem mal ein kleines "Programm" geschrieben, dass du siehst wie ich das meine.
Ist mit Sicherheit noch ausbaufähig, aber was solls :P

Logger.h

Code: Alles auswählen

#ifndef LOGGER_H
#define LOGGER_H

#include <QtGui>

class Logger : public QObject
{
    Q_OBJECT
    public slots:
        // Slot, um ein Log zu loggen...
        void log( const QString& message )
        {
            m_logs.append( message );
            emit newLog( message );
        }
    public:
        // statische Logger-Instanz
        static Logger* instance()
        {
            static Logger *logger;
            if(!logger) {
                logger = new Logger;
                QObject::connect(qApp, SIGNAL(lastWindowClosed()), logger, SLOT(deleteLater()));
            }
            return logger;
        }
        
        // statische logWidget-Instanz
        static QTextEdit* logWidget()
        {
            static QTextEdit *edit;
            if(!edit) {
                edit = new QTextEdit;
                QObject::connect(qApp, SIGNAL(lastWindowClosed()), edit, SLOT(deleteLater()));
            }
            return edit;
        }

        // Wenn das Log in dem Logwidget erscheinen soll -> on =true.
        static void logToLogWidget( bool on=true )
        {
            if(on) {
                QObject::connect(Logger::instance(), SIGNAL(newLog(const QString&)),
                                 Logger::logWidget(), SLOT(append(const QString&)));
            } else {
                QObject::disconnect(Logger::instance(), SIGNAL(newLog(const QString&)),
                                   Logger::logWidget(), SLOT(append(const QString&)));
            }
        }
    signals:
        void newLog( const QString& );
    private:
        QStringList m_logs;
        Logger()
        {}
};

class Window : public QWidget
{
    Q_OBJECT
    public:
        Window()
        {
            QVBoxLayout *l = new QVBoxLayout(this);
            QPushButton *btn = new QPushButton("Blubberboy", this);
            l->addWidget( btn );
            l->addWidget( Logger::logWidget() );
            QObject::connect(btn, SIGNAL(clicked()), this, SLOT(debugClick()));
        }

    public slots:
        void debugClick()
        {
            qDebug() << "Button clicked()";
        }
};

#endif
main.cpp

Code: Alles auswählen


#include "Logger.h"

#include <QtGui>

void myMessageOutput(QtMsgType type, const char *msg) 
{ 
    QString s;
    switch (type) { 
        case QtDebugMsg: 
            s = "Qt Debug:";
            break; 
        case QtWarningMsg: 
            s = "Qt Warning:"; 
            break; 
        case QtCriticalMsg: 
            s = "Qt Critical:"; 
            break; 
        case QtFatalMsg: 
            s = "Qt Fatal:";
    } 
    s.append(msg);
    Logger::instance()->log(s);
}

int main( int argc, char **argv )
{
    qInstallMsgHandler(myMessageOutput);
    QApplication app( argc, argv );
    Logger::logToLogWidget();
    Window w;
    w.show();
    return app.exec();
}
Wenn du gleich den Umweg über ein "LogFile" gehen willst, mach das doch über QFileSystemWatcher!
Ich würdes aber nicht machen. Wenn du es mal in ne Datei schreiben willst, gib dem Logger eine Methode "sync()" oder so, die m_logs in eine Datei schreibt. fertig.

Grüße
Franz

Danke

Verfasst: 23. April 2009 13:09
von CKassiopaia
besten dank!
ich werde das schnellstmöglichst testen, bin nur grad bei nem anderen Problem ;-)

Antwort folgt...

So....zurück zum Ursprünglichen Problem ,-)

Verfasst: 20. Mai 2009 12:45
von CKassiopaia
So...ich versuche mich grade in dein Programm reinzudenken...will ja schließlich auch verstehen, was ich eigentlich mache.

Einige Fragen:
1. Gehe ich richtig davon aus, dass nur qDebug() (und qWarning etc...) in dein Textfeld kommt?
2. Die Verbindung läuft über den Msg-Handler, den du in der main.cpp geschrieben hast? (Logger::instance()->log(s)?)
3. Schreibst du erst alles in m_logs, sendest dann das signal newLog welches die Ausgaben in das Textfeld schreibt? Könnte man auch direkt in dem Msg-handler machen? Ist das Sinnvoll?

So..Wochenende und Montag werd ich das Umsetzen....! Besten Dank!

Re: So....zurück zum Ursprünglichen Problem ,-)

Verfasst: 20. Mai 2009 12:59
von franzf
CKassiopaia hat geschrieben: 1. Gehe ich richtig davon aus, dass nur qDebug() (und qWarning etc...) in dein Textfeld kommt?
Nö, alles was du dem Logger::instance() an log() gibst, kommt in das QTextEdit:

Code: Alles auswählen

static void logToLogWidget( bool on=true ) 
        { 
            if(on) { 
                QObject::connect(Logger::instance(), SIGNAL(newLog(const QString&)), 
                                 Logger::logWidget(), SLOT(append(const QString&))); 
            } else { 
                QObject::disconnect(Logger::instance(), SIGNAL(newLog(const QString&)), 
                                   Logger::logWidget(), SLOT(append(const QString&))); 
            } 
        }
Sobald du im Code ein Logger::logToLogWidget(true); ausführst, werden alle Logs ins TextEdit geschrieben, bei false ist mit Ausgabe schluss!
2. Die Verbindung läuft über den Msg-Handler, den du in der main.cpp geschrieben hast? (Logger::instance()->log(s)?)
Nein, die Verbindung läuft in logToLogWidget(), falls du die Verbindung zwischen Logger<->LogWidget meinst.
Der Handler sorgt nur dafür, dass alles, was an Messages über qWarnign() usw. ankommt an das Log angehangen wird.
3. Schreibst du erst alles in m_logs, sendest dann das signal newLog welches die Ausgaben in das Textfeld schreibt? Könnte man auch direkt in dem Msg-handler machen? Ist das Sinnvoll?
Sobald du ein

Code: Alles auswählen

Logger::instance()->log("Neues Log");
machst, wird der String an m_logs angehangen und sofort das Signal newLog() versandt.

Ich will das nicht im MsgHandler machen, da ich mir denke, du brachst keine eigene Log-Klasse, nur um die Qt-Messages abzufangen ;)
Vllt. willst du in eigenen Klassen auch normale Logs ohne der Kennzeichnung mit "Qt Debug" ausgeben?
Oder am Ende sogar eine richtige Log-Klasse schreiben, die neben einem msgString() auch noch einen title(), einen timeStamp() und ein logLevel() hat? Das ist deutlich mehr als ein QString, und kann im MsgHandler nimmer ordentlich gehandhabt werden...

Ciao
Franz

Verfasst: 25. Mai 2009 08:34
von CKassiopaia
Zu 1:
Ich wollte damit sagen, dass keine cout&acute;s und printf&acute;s oder so in dem Textfeld erscheinen.... also das quasi nicht die stdout und stderr umgeleitet werden, sondern nur die qDebugFunktion (wenn man das so formulieren kann)

Zu 2:
Mit "Verbindung" meinte ich die qDebug -> Textfeld....

Zu 3:
So ungefähr meinte ich das.... :-)

So...und nu hab ich das versucht nachzubasteln: Wollte das in mein Projekt integrieren und nicht unbedingt eine eigene Logger-Klasse sondern einfach die Funktionen in meiner Hauptklasse integrieren...

Aber wenn ich die qInstallMsgHandler(myMsgOutput) aufrufe (und vorher die Funktione als MainClass::myMsgOutput erstellt habe meint der Compiler, dass die Deklarationen nicht zueinander passen....

argument of type 'void (MainClass::)(QtMsgType, const char*)' does not match 'void (*)(QtMsgType, const char*)';

Ist das nicht möglich, innerhalb einer klasse den MsgHandler zu verändern?

Und wie kann ich auch noch die cout&acute;s und printf&acute;s also die gesamte stdout umleiten?

Danke![/quote]

Verfasst: 25. Mai 2009 08:53
von franzf
Sicherlich kannst du in deiner Klasse auch den MsgHandler SETZEN, aber nicht auf eine Methode deiner Klasse :P
Ich würde es allerdings am Anfang der main machen, dass du auch wirklich alle Ausgaben erwischst!
Du übergibst da ja einen Klassenmethodenpointer. Das geht zwar scheinbar mit Boost, ist aber nicht so simpel :)
Schreib dir das doch in ne ungebundene, statische C-Methode, kannst die ja erst in MainClass.ccp deklarieren. Und dann hast du wieder das Problem, dass du aus der Methode nicht auf deinen Klassenmember zugreifen kannst :P
Dann definierst du dir in irgend einem Header eine Methode, die dir ein statisches (also immer das gleiche) QTextEdit* zurückliefert, so wie das in meinem Beispiel die Klasse Logger::logWidget() getan hat.

Wie du den ganzen stdout-Traffic mitkriegst weiß ich nicht, aber wenn du konsequent auf qDebug() für informativen Output setzt, brauchst du das eh nicht.

Grüße
Franz