Domanda

I'm in the process of "porting" my CLI application to a GUI one using Qt. I basically set up every options of the program in the GUI, then push a button that runs the main function of the program. In the CLI version I have some std::cout that report progress in console. I need to report progress to user in the GUI too, but I'm not sure what would be the best way.

I tried the easy way, which is redirecting cout to QTextEdit using this, but it only updates at the end with the all output.

I could replace every cout by QText but I'd also like to run the app in console with progress output in console .. , when the program is called with arguments.

So my question is, is there a way, design pattern, tool, that would allow me to call something like reportProgress("I am here"); that would display in QText if called from GUI and output in console if called from console (or with arguments) ? Or, do I have to write 2 versions of everything that report progress in my program ?

È stato utile?

Soluzione

The method I use to report to multiple types of output is to use an abstract "Logger" base class. Then, inherit classes from Logger, one for GUI and for CLI or whatever else you want to log to. Every object that sends to the log simply emits a Log(const QString&), which is picked up by the relevant class.

/// An interface to be implemented by loggers.
class ILogger : public QObject
{
   Q_OBJECT
public:
   ILogger(QObject* parent = {});
   Q_SLOT virtual void Log(const QString& txt) = 0;
};

ILogger::ILogger(QObject* parent) : QObject(parent) {}
/// Logs to a QPlainTextEdit
class TextBoxGUILogger : public ILogger
{
   Q_OBJECT
public:
   TextBoxGUILogger(QObject* parent = {});
   void setWidget(QPlainTextEdit*);
   void Log(const QString& txt) override;
private:
   QPointer<QTextEdit> m_txtEditBox;
};

TextBoxGUILogger::TextBoxGUILogger(QObject* parent) : ILogger(parent) {}

void TextBoxGUILogger::setWidget(QPlainTextEdit* edit) 
{
   m_txtEditBox = edit;
}

void TextBoxGUILogger::Log(const QString& txt) 
{
   if (m_txtEditBox) m_txtEditBox->append(txt);
}
/// Logs to the standard output.
class CLILogger : public ILogger
{
   Q_OBJECT
public:
   CLILogger(QObject* parent = {});
   void Log(const QString& txt) override;
};

CLILogger::CLILogger(QObject* parent) : ILogger(parent) {}

void CLILogger::Log(const QString& txt) 
{
   printf("%s", txt.toLocal8Bit().constData());
}
/// Logs to a file.
class FileLogger : public ILogger
{
   Q_OBJECT
public:
   FileLogger(QObject* parent = {});
   /// The file can be owned by another object, or it can be made a child
   /// of the logger. In either case the behavior will be correct.
   void setFile(QFile*);
   void Log(const QString& txt) override;
private:
   QPointer<QFile> m_file;
};

FileLogger::FileLogger(QObject* parent) : ILogger(parent) {}

void FileLogger::setFile(QFile* file) 
{
   m_file = file;
}

void FileLogger::Log(const QString& txt) 
{
   if (m_file)
       m_file->write(txt.toLocal8Bit().constData());
}

So, you can now create the relevant logger...

For standard output (CLI): -

ILogger* pLogger = new CLILogger;

Or QPlainTextEdit: -

ILogger* pLogger = new TextBoxGuiLogger;

Finally, to a file: -

ILogger* pLogger = new FileLogger;

Each class that is going to log connects to the logger: -

// Qt 5 connect syntax
connect(someClass, &SomeClass::Log, pLogger, &ILogger::Log);

This allows you to create one or more logging objects at the same time and the objects that log don't care what it is that is doing the logging, they just emit a signal: -

emit Log("Log this text");

Altri suggerimenti

There is a QProgressBar which does what you want. It is a GUI element though, as far as I know, there's nothing for the CLI.

As far as I know there is no such built-in API in Qt, but you can easily implement it in your reportProgress() function. When you create your QApplication, you can define its type in constructor, depending on whether it console or GUI application:

QApplication::QApplication(int & argc, char ** argv, Type type)

where Type can ba either QApplication::Tty, or QApplication::GuiClient etc.

In your reportProgress() function you can query the type of the application with QApplication::type() function, like:

void reportProgress() {
    QApplication::Type t = QApplication::type();
    if (t == QApplication::Tty) {
        // console output
    } else {
        // output to text edit, etc.
    }
    [..]
}

In Windows you can simply pipe the output of the GUI program through cat. Well you have to create or install a cat first, but that's trivial. Alternatively you can build the program as console subsystem, or use GUI subsystem and let the program create a console.

There's no real need to do anything more fancy, but if that feels desirable, then a simple and good way to decouple things in Windows is to post progress events to the GUI by way of Windows mailslots.

Summing up: pipe the output is simplest, then creating a console as next most simple (a single API call, or building as console subsystem), then as most complex, adding an event-driven progress GUI.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top