How to make a QTextStream stream to QTextBrowser
-
My application can interact with users in either console or GUI mode. Both modes have the sole purpose of collecting user inputs (source file, destination file, etc etc...), do some verifications, and then pass those inputs as parameters to the function that will take care of the computations.
However, there are 2 parameters that aren't user inputs. These are
outStream
anderrStream
, and they are used by the computation function to report current status and errors. Both parameters are of typeQTextStream
.If in console mode, the function will receive
QTextStream outStream(stdout)
andQTextStream outStream(stderr)
, effectively sending the messages to stdout and stderr.If in GUI mode, I want those message to appear in a
QTextBrowser
(with the error messages in red, but whatever). To do this, I wrote a classTextBrowserStream
that extends QTextStream and implements (overrides?) the<<
operator. This solution behaves as intended as long as I stay in the MainWindow method where the TextBrowserStream instances were created. When those instances are passed in argument, all I get is messages on stderr saying "QTextStream: No device".I searched for solutions, and notably found those two posts, presenting a few solutions in the replies:
- Streaming to QTextEdit via QTextStream
- [SOLVED] Streaming Text Continuously to QTextEdit Without Creating File
Two problems:
- My situation requires the use of QTextStream because this is how the reports are sent to stdout and stderr when in console mode. This requirement isn't part of the posts I linked.
- I am a beginner in both Qt and C++ and I cannot discern wether those solutions can be applied to my situation, or if they're fundamentally flawed, or if it's my basic design (using QTextStream to print on either stdout/stderr or a QTextBrowser) that doomed me from the start.
Code snippets
I tried to edit the snippets down to the minimum, so I omitted most of the include directives, notably.
main.cpp
int main(int argc, char *argv[]) { if (argc == 1) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } else { return mainConsole(argc, argv); } }
mainconsole.cpp
int mainConsole(int argc, char *argv[]) { QTextStream outStream(stdout); QTextStream errStream(stderr); return doItAll(outStream, errStream); //terrific name, I know }
mainwindow.cpp
#include "textbrowserstream.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_buttonBox_accepted() { TextBrowserStream outStream(*ui->statusTextBrowser, Qt::GlobalColor::black); TextBrowserStream errStream(*ui->statusTextBrowser, Qt::GlobalColor::red); QString const HelloCpp = "Hello C++"; outStream << HelloCpp << endl; // Will appear in statusTextBrowser doItAll(outStream, errStream); }
doitall.cpp
#include "textbrowserstream.h" int doItAll(QTextStream &outStream, QTextStream &errStream) { QString const HelloQt = "Hello Qt"; outStream << HelloQt << endl; // When called by mainConsole, will send "Hello Qt" to stdout, as intended // When called by MainWindow, will send "QTextStream: No device" to stderr (!) return 0; }
textbrowserstream.h
This one is an unedited copy-pasta from my code, since this is where I am struggling.
#ifndef TEXTBROWSERSTREAM_H #define TEXTBROWSERSTREAM_H #include <QWidget> #include <QTextStream> #include <QTextBrowser> #include <QColor> class TextBrowserStream : public QTextStream { QTextBrowser &browser; QColor const &color; public: TextBrowserStream(QTextBrowser &browser, QColor const &color = Qt::GlobalColor::black) : browser(browser), color(color) { } // Based on gregseth's answer at https://stackoverflow.com/a/2351479 TextBrowserStream &operator<<(QString const &string) { browser.setTextColor(color); browser.append(string); return *this; } }; #endif // TEXTBROWSERSTREAM_H
Final Notes
- My code compiles, and my app outputs correct results both in console and GUI. The current issue is strictly a matter of communicating progress or errors to the user
- I am using both Qt 5.9.5 and 5.15.0 to build (long story), and the two of them have the same behaviour
- Let me know if more code snippets are needed
- Thank you for your time
-
TextBrowserStream &operator<<(QString const &string)
does not overrideQTextStream &operator<<(QString const &string)
so there is no whydoItAll
even knows the first function exists.What you should do, instead of subclassing the stream is to subclass a
QIODevice
, overridewrite()
so that it writes to the text browser and then use a normalQTextStream
on such device.The quick-and-easy solution, however is have a
QBuffer
operate on aQByteArray
, use theQBuffer
as device forQTextStream
then connect to theQBuffer::channelBytesWritten
signal to know when new data was writted by the QTextStream and copy that data to he text browser -
My application can interact with users in either console or GUI mode. Both modes have the sole purpose of collecting user inputs (source file, destination file, etc etc...), do some verifications, and then pass those inputs as parameters to the function that will take care of the computations.
However, there are 2 parameters that aren't user inputs. These are
outStream
anderrStream
, and they are used by the computation function to report current status and errors. Both parameters are of typeQTextStream
.If in console mode, the function will receive
QTextStream outStream(stdout)
andQTextStream outStream(stderr)
, effectively sending the messages to stdout and stderr.If in GUI mode, I want those message to appear in a
QTextBrowser
(with the error messages in red, but whatever). To do this, I wrote a classTextBrowserStream
that extends QTextStream and implements (overrides?) the<<
operator. This solution behaves as intended as long as I stay in the MainWindow method where the TextBrowserStream instances were created. When those instances are passed in argument, all I get is messages on stderr saying "QTextStream: No device".I searched for solutions, and notably found those two posts, presenting a few solutions in the replies:
- Streaming to QTextEdit via QTextStream
- [SOLVED] Streaming Text Continuously to QTextEdit Without Creating File
Two problems:
- My situation requires the use of QTextStream because this is how the reports are sent to stdout and stderr when in console mode. This requirement isn't part of the posts I linked.
- I am a beginner in both Qt and C++ and I cannot discern wether those solutions can be applied to my situation, or if they're fundamentally flawed, or if it's my basic design (using QTextStream to print on either stdout/stderr or a QTextBrowser) that doomed me from the start.
Code snippets
I tried to edit the snippets down to the minimum, so I omitted most of the include directives, notably.
main.cpp
int main(int argc, char *argv[]) { if (argc == 1) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } else { return mainConsole(argc, argv); } }
mainconsole.cpp
int mainConsole(int argc, char *argv[]) { QTextStream outStream(stdout); QTextStream errStream(stderr); return doItAll(outStream, errStream); //terrific name, I know }
mainwindow.cpp
#include "textbrowserstream.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_buttonBox_accepted() { TextBrowserStream outStream(*ui->statusTextBrowser, Qt::GlobalColor::black); TextBrowserStream errStream(*ui->statusTextBrowser, Qt::GlobalColor::red); QString const HelloCpp = "Hello C++"; outStream << HelloCpp << endl; // Will appear in statusTextBrowser doItAll(outStream, errStream); }
doitall.cpp
#include "textbrowserstream.h" int doItAll(QTextStream &outStream, QTextStream &errStream) { QString const HelloQt = "Hello Qt"; outStream << HelloQt << endl; // When called by mainConsole, will send "Hello Qt" to stdout, as intended // When called by MainWindow, will send "QTextStream: No device" to stderr (!) return 0; }
textbrowserstream.h
This one is an unedited copy-pasta from my code, since this is where I am struggling.
#ifndef TEXTBROWSERSTREAM_H #define TEXTBROWSERSTREAM_H #include <QWidget> #include <QTextStream> #include <QTextBrowser> #include <QColor> class TextBrowserStream : public QTextStream { QTextBrowser &browser; QColor const &color; public: TextBrowserStream(QTextBrowser &browser, QColor const &color = Qt::GlobalColor::black) : browser(browser), color(color) { } // Based on gregseth's answer at https://stackoverflow.com/a/2351479 TextBrowserStream &operator<<(QString const &string) { browser.setTextColor(color); browser.append(string); return *this; } }; #endif // TEXTBROWSERSTREAM_H
Final Notes
- My code compiles, and my app outputs correct results both in console and GUI. The current issue is strictly a matter of communicating progress or errors to the user
- I am using both Qt 5.9.5 and 5.15.0 to build (long story), and the two of them have the same behaviour
- Let me know if more code snippets are needed
- Thank you for your time
@Amelia-SZK said in How to make a QTextStream stream to QTextBrowser:
int doItAll(QTextStream &outStream, QTextStream &errStream)
Change it to
int doItAll(QTextStream *outStream, QTextStream *errStream)
-
TextBrowserStream &operator<<(QString const &string)
does not overrideQTextStream &operator<<(QString const &string)
so there is no whydoItAll
even knows the first function exists.What you should do, instead of subclassing the stream is to subclass a
QIODevice
, overridewrite()
so that it writes to the text browser and then use a normalQTextStream
on such device.The quick-and-easy solution, however is have a
QBuffer
operate on aQByteArray
, use theQBuffer
as device forQTextStream
then connect to theQBuffer::channelBytesWritten
signal to know when new data was writted by the QTextStream and copy that data to he text browser -
TextBrowserStream &operator<<(QString const &string)
does not overrideQTextStream &operator<<(QString const &string)
so there is no whydoItAll
even knows the first function exists.What you should do, instead of subclassing the stream is to subclass a
QIODevice
, overridewrite()
so that it writes to the text browser and then use a normalQTextStream
on such device.The quick-and-easy solution, however is have a
QBuffer
operate on aQByteArray
, use theQBuffer
as device forQTextStream
then connect to theQBuffer::channelBytesWritten
signal to know when new data was writted by the QTextStream and copy that data to he text browser@VRonin I subclassed a
QIODevice
with this stackoverflow answer (with some modifications for color support), and it worked. My outStream messages appeared in black in the QTextBrowser, and my errStream messages appeared in red, as intended.However, the messages are meant to inform the user on how the computations are progressing, and currently they only appear all together once
doItAll
has returned. Is that normal for a QIODevice?Would using your
QBuffer
solution fix this problem? I tried to do it, but couldn't manage to connect the signals and do something with them.Updated code snippets
QIODevice
This is my current implementation, using the QIODevice solution.
mainwindow.cpp
TextBrowserStream
has been replaced byQTextStream
.#include "texteditiodevice.h" // constructor and destructor omitted void MainWindow::on_buttonBox_accepted() { // User has clicked on OK button // Credits to TimW https://stackoverflow.com/a/2354778 QTextStream outStream(new TextEditIoDevice(ui->statusTextBrowser, this, Qt::GlobalColor::black)); QTextStream errStream(new TextEditIoDevice(ui->statusTextBrowser, this, Qt::GlobalColor::red)); QString const HelloCpp = "Hello C++"; outStream << HelloCpp << endl; // Will appear in statusTextBrowser doItAll(outStream, errStream); }
doitall.cpp
Updated to show the new behaviour.
int doItAll(QTextStream &outStream, QTextStream &errStream) { QString const Start = "Starting..."; QString const End = "Finished."; outStream << Start << endl; // Should appear immediately after clicking OK. // Instead, it appears ~4 seconds after /* Computations taking ~4 seconds... */ outStream << End << endl; // Should appear ~4 seconds after clicking OK return 0; }
texteditiodevice.h
Just like
TextBrowserStream
in the original snippets, this one is an unedited copy-paste.#ifndef TEXTEDITIODEVICE_H #define TEXTEDITIODEVICE_H #include <QObject> #include <QTextBrowser> #include <QPointer> // Credits to TimW https://stackoverflow.com/a/2354778 class TextEditIoDevice : public QIODevice { Q_OBJECT public: TextEditIoDevice(QTextEdit *const textEdit, QObject *const parent, QColor color) : QIODevice(parent), textEdit(textEdit), color(color) { open(QIODevice::WriteOnly | QIODevice::Text); } protected: qint64 readData(char *data, qint64 maxSize) { return 0; } qint64 writeData(const char *data, qint64 maxSize) { if (textEdit) { textEdit->setTextColor(color); textEdit->insertPlainText(data); } return maxSize; } private: QPointer<QTextEdit> textEdit; QColor color; }; #endif // TEXTEDITIODEVICE_H
QBuffer
This snippet describe my attempt to use the QBuffer solution. It compiled, but during runtime, the "Hello C++" printed before the call to
doItAll
stopped appearing, and I got those messages on stderr:QObject::connect: No such signal QBuffer::channelBytesWritten() QObject::connect: (receiver name: 'MainWindow')
mainwindow.cpp
This is how I tried to implement the QBuffer solution. This code is no longer part of the "current" implementation.
// constructor and destructor omitted void MainWindow::on_buttonBox_accepted() { // User has clicked on OK button QBuffer outBuffer(this); QBuffer errBuffer(this); outBuffer.open(QIODevice::ReadWrite | QIODevice::Text); errBuffer.open(QIODevice::ReadWrite | QIODevice::Text); QTextStream outStream(&outBuffer); QTextStream errStream(&errBuffer); connect(&outBuffer, SIGNAL(channelBytesWritten()), this, SLOT(on_stream_write(&outBuffer, Qt::GlobalColor::black))); connect(&errBuffer, SIGNAL(channelBytesWritten()), this, SLOT(on_stream_write(&outBuffer, Qt::GlobalColor::red))); QString const HelloCpp = "Hello C++"; outStream << HelloCpp << endl; // Will appear in statusTextBrowser doItAll(outStream, errStream); } void MainWindow::on_stream_write(QBuffer &buffer, Qt::GlobalColor color) { ui->statusTextBrowser->setTextColor(color); while (!buffer.atEnd()) { ui->statusTextBrowser->append(buffer.readLine()); } }
So I guess that my follow-up questions are:
- Is it normal & expected that the QIODevice doesn't print to QTextBrowser before doItAll returns?
- Can this be fixed with signals and slots?
- When I tried to implement the QBuffer solution, was I on the right track?
Thank you for your time
-
Hi,
Your doItAll function is blocking the event loop, therefore no repaint is done while it's doing its stuff hence you only see the messages once done.