QTextDocument in thread issue
-
Hello!
I want to display the print preview dialog with some data before printing. The data is very big, so it freezes. I have added it to a thread and added the progress dialog but the progress dialog seems also freeze. I have found the issue is with
QTextDocument setPlainText
method in GUI (main thread). So, I added it in the thread as well.I created the QTextDocument object in the thread, setPlainText("some data") there. Now, I have issue when I emit
QTextDocument
object from a worker class to use it in main thread in the following lambda connect:connect(printPreviewDlg, &QPrintPreviewDialog::paintRequested, this, [txtDoc](QPrinter *printer) { txtDoc->print(printer); });
It crashes with a read access violation error. I need this object to call
print
method inQPrintPreviewDialog
connect. Any ideas/examples how to sendQTextDocument
object between worker/main GUI threads properly? Thank you. -
Hi,
Are you sure txtDoc is a valid pointer ?
-
From this docs: https://doc.qt.io/qt-5/threads-modules.html I have found that I can pass a copy between threads by using the
clone
method. I tried it but it returns:QObject: Cannot create children for a parent that is in a different thread. (Parent is QTextDocument(0x2314adccdf0), parent's thread is QThread(0x2314b524020), current thread is QThread(0x23139204630)
-
Yes, it is valid. I initialized it, set the data and emit it from a worker.
Code:
QFont docFont; docFont.setPointSize(14); QTextDocument *textDoc = new QTextDocument(this); textDoc->setDefaultFont(docFont); textDoc->setPlainText("some data"); emit generatedPreview(textDoc); // crashes in the slot emit generatedPreviewData(textDoc->clone()); //works but returns QObject: Cannot create children for a parent that is in a different thread. (Parent is QTextDocument(0x2314adccdf0), parent's thread is QThread(0x2314b524020), current thread is QThread(0x23139204630)
-
Why not create the QTextDocument in your thread and then move it to the main thread once it's ready ?
-
@SGaist said in QTextDocument in thread issue:
Why not create the QTextDocument in your thread and then move it to the main thread once it's ready ?
What do you mean by
your thread
? I created it in a worker class and emit it to the main thread, then it crashes. When I pass it from a worker to main thread as a clone it displays the following issue:QObject: Cannot create children for a parent that is in a different thread.
.I can create QTextDocument object in the main thread, pass it to worker,
setPlainText
there and then emitQTextDocument
object back to main thread? -
If by passing you mean updating a pointer value, then you are not moving things around.
Since QTextDocument is a QObject, hence the suggestion of using moveToThread.
-
Since you create it in your secondary thread, you need to push it to your main thread.
-
@Cobra91151 QTextDocument should not live in a second thread so you get that error. QTextDocument will interact with QObjects that live in the main thread (for example QTextEdit, QPrinter, etc).
-
From this old post, I can use
QTextDocument
object in worker class (second thread) and pass it between the threads: https://www.qt.io/blog/2007/09/27/multi-threaded-text-layout-and-printingIt contains the old thread design but I want to check out the full source code. Where I can find this snapshot example? Thank you.
-
Based on the name, I would go with the Qt 4 sources of Assistant.
-
@eyllanesc said in QTextDocument in thread issue:
@Cobra91151 QTextDocument should not live in a second thread so you get that error. QTextDocument will interact with QObjects that live in the main thread (for example QTextEdit, QPrinter, etc).
I had
QTextDocument
in my main thread butsetPlainText
method freezes theGUI
for a couple of seconds. So, I need to fix it. That is why I put it to the worker (second thread). Thanks. -
@Cobra91151 Qt UI classes should not be used in other threads than main (UI) thread!
How big is the text you're trying to load? -
It is 280 pages for printing. I run some debugging and found out that
setPlainText
does not lead to such issue. The actual problem is withprint
method. It takes too much time to paint all these pages to theQPrintPreviewDialog
.Code:
connect(printPreviewDlg, &QPrintPreviewDialog::paintRequested, this, [txtDoc](QPrinter *printer) { txtDoc->print(printer); });
I will subclass the
QTextDocument
and reimplement theprint
method to display some progress dialog. Thanks. -
Nope, it does not work. The progress dialog still freezing.
-
@Cobra91151 Can you show ther code?
-
@Cobra91151
Also for 280 pages just how long does it take/freeze? Oh you said "a couple of seconds". Does your end-user not have the patience to wait 2 seconds for 280 pages? :) If the user prints to printer how long does it take? Users are over-pampered these days.... -
Here is my test example:
printexample.h
#ifndef PRINTEXAMPLE_H #define PRINTEXAMPLE_H #include <QWidget> #include <QPushButton> #include <QHBoxLayout> #include <QThread> #include <QPrinter> #include <QPrintPreviewDialog> #include <QToolBar> #include <QTextDocument> #include <QDebug> #include "printworker.h" class PrintExample : public QWidget { Q_OBJECT public: PrintExample(QWidget *parent = nullptr); ~PrintExample(); private slots: void printData(); void setPrintPreviewData(QString printData); void setPreviewDataGenerationFailed(); private: QLayout *printLayout(QPushButton *btn); }; #endif // PRINTEXAMPLE_H
printexample.cpp
#include "printexample.h" PrintExample::PrintExample(QWidget *parent) : QWidget(parent) { setWindowTitle("Print example"); setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint); setFixedSize(500, 400); QPushButton *printBtn = new QPushButton("Print", this); printBtn->setFixedSize(120, 50); connect(printBtn, &QPushButton::clicked, this, &PrintExample::printData); setLayout(printLayout(printBtn)); } QLayout *PrintExample::printLayout(QPushButton *btn) { QHBoxLayout *btnLayout = new QHBoxLayout(); btnLayout->setAlignment(Qt::AlignHCenter); btnLayout->addWidget(btn); return btnLayout; } void PrintExample::printData() { //This is just test example data QString testBigData = "QTextDocument is a container for structured rich text documents, providing support for styled text and various types of document elements, such as lists, tables, " "frames, and images. They can be created for use in a QTextEdit, or used independently. Each document element is described by an associated format object. " "Each format object is treated as a unique object by QTextDocuments, and can be passed to objectForFormat() to obtain the document element that it is applied to. " "A QTextDocument can be edited programmatically using a QTextCursor, and its contents can be examined by traversing the document structure. " "The entire document structure is stored as a hierarchy of document elements beneath the root frame, found with the rootFrame() function. " "Alternatively, if you just want to iterate over the textual contents of the document you can use begin(), end(), and findBlock() to retrieve text blocks that you can " "examine and iterate over. The layout of a document is determined by the documentLayout(); you can create your own QAbstractTextDocumentLayout subclass and set it using " "setDocumentLayout() if you want to use your own layout logic. The document's title and other meta-information can be obtained by calling the metaInformation() " "function. For documents that are exposed to users through the QTextEdit class, the document title is also available via the QTextEdit::documentTitle() " "function. The toPlainText() and toHtml() convenience functions allow you to retrieve the contents of the document as plain text and HTML. " "The document's text can be searched using the find() functions. Undo/redo of operations performed on the document can be controlled using the setUndoRedoEnabled() " "function. The undo/redo system can be controlled by an editor widget through the undo() and redo() slots; the document also provides contentsChanged(), " "undoAvailable(), and redoAvailable() signals that inform connected editor widgets about the state of the undo/redo system. " "The following are the undo/redo operations of a QTextDocument."; QThread *printDataThread = new QThread(); PrintWorker *printWorker = new PrintWorker(); printWorker->moveToThread(printDataThread); connect(printWorker, &PrintWorker::generatedPreviewData, this, &PrintExample::setPrintPreviewData); connect(printWorker, &PrintWorker::previewDataGenerationFailed, this, &PrintExample::setPreviewDataGenerationFailed); connect(printWorker, &PrintWorker::finished, printDataThread, &QThread::quit); connect(printWorker, &PrintWorker::finished, printWorker, &PrintWorker::deleteLater); connect(printDataThread, &QThread::finished, printDataThread, &QThread::deleteLater); QMetaObject::invokeMethod(printWorker, "generatePrintPreview", Q_ARG(QString, testBigData)); printDataThread->start(); } void PrintExample::setPrintPreviewData(QString printData) { QPrinter *previewPrinter = new QPrinter(QPrinter::HighResolution); previewPrinter->setOutputFormat(QPrinter::NativeFormat); previewPrinter->setPaperSize(QPrinter::A4); previewPrinter->setPageMargins(QMarginsF(15, 15, 15, 15)); QPrintPreviewDialog *printPreviewDlg = new QPrintPreviewDialog(previewPrinter, this); printPreviewDlg->setGeometry(100, 100, 700, 500); printPreviewDlg->setMinimumSize(700, 500); printPreviewDlg->setWindowModality(Qt::ApplicationModal); printPreviewDlg->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint); printPreviewDlg->installEventFilter(this); QFont docFont; docFont.setPointSize(14); QTextDocument *textDoc = new QTextDocument(this); textDoc->setDefaultFont(docFont); textDoc->setPlainText(printData); connect(printPreviewDlg, &QPrintPreviewDialog::paintRequested, this, [this, textDoc](QPrinter *printer) { textDoc->print(printer); }); connect(printPreviewDlg, &QPrintPreviewDialog::finished, this, [textDoc, previewPrinter, printPreviewDlg]() { textDoc->deleteLater(); delete previewPrinter; printPreviewDlg->deleteLater(); }); printPreviewDlg->show(); } void PrintExample::setPreviewDataGenerationFailed() { //Failed to generate preview } PrintExample::~PrintExample() { }
printworker.h:
#ifndef PRINTWORKER_H #define PRINTWORKER_H #include <QObject> class PrintWorker : public QObject { Q_OBJECT public: explicit PrintWorker(QObject *parent = nullptr); public slots: void generatePrintPreview(QString previewData); signals: void generatedPreviewData(QString data); void previewDataGenerationFailed(); void finished(); }; #endif // PRINTWORKER_H
printworker.cpp:
#include "printworker.h" PrintWorker::PrintWorker(QObject *parent) : QObject(parent) { } void PrintWorker::generatePrintPreview(QString previewData) { if (!previewData.isEmpty()) { emit generatedPreviewData(previewData); } else { emit previewDataGenerationFailed(); } emit finished(); }
Screenshot:
So, it does not freeze much for 1 page. Anyway, I want to display some progress dialog before the preview dialog. Any ideas? Thanks.