Reputation: 204
I have this demo program that is not working as I would wish:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtPrintSupport import *
from PyQt5.QtWebEngineWidgets import *
class Runnable(QRunnable):
def __init__(self, window, mode):
super(Runnable, self).__init__()
self.window = window
self.mode = mode
self.html = '<!DOCTYPE HTML><html><body><p>test</p></body></html>'
self.report_filename = 'report.pdf'
def run(self):
if self.mode == 'sync':
# this works ok
printer = QPrinter()
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setPaperSize(QPrinter.A4)
printer.setOutputFileName(self.report_filename)
doc = QTextDocument()
doc.setHtml(self.html)
#doc.setPageSize(printer.pageRect().size())
doc.print(printer)
print('pdf file created')
elif self.mode == 'async':
# this doesn't work
self.page = QWebEnginePage()
self.page.loadFinished.connect(self.on_load_finished)
loadFinished_works_for_setHtml = False
if loadFinished_works_for_setHtml:
# async func, but no loadFinished signal in docs, too bad
self.page.setHtml(self.html)
else:
# silly, all because no loadFinished signal for setHtml()
with open('report.html', 'w', encoding='utf-8') as f:
f.write(self.html)
url = QUrl('file:report.html')
print('url.isLocalFile():', url.isLocalFile())
self.page.load(url)
def on_load_finished(self, ok):
# 1. problem: This method never executes, why?
# I tried with QWebEngineView() too, but without luck.
# 2. problem: If this method somehow executes, it will run in main thread,
# but self.page.printToPdf() can be slow, so I want this to also
# run in runnable thread or some other thread, but not in main thread
print('load finished, ok: ', ok)
#self.page.pdfPrintingFinished.connect(self.on_pdf_printing_finished)
#page_layout = QPageLayout(QPageSize(QPageSize.A4), QPageLayout.Portrait, QMarginsF(20, 20, 20, 20), QPageLayout.Millimeter, minMargins = QMarginsF(0, 0, 0, 0))
#self.page.printToPdf(self.report_filename, page_layout)
#def on_pdf_printing_finished(self, file_path, ok):
# print('printToPdf finished', file_path, ok)
# # send signal to main thread or open pdf file with subprocess.Popen() or sth.
# # 1a. problem: I want this (for example opening pdf file) to also run in
# # runnable thread or some other thread, but not in main thread
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.print_sync_button = QPushButton('Make PDF (sync)', self)
self.print_async_button = QPushButton('Make PDF (async)', self)
self.print_sync_button.clicked.connect(lambda : self.handle_print('sync'))
self.print_async_button.clicked.connect(lambda : self.handle_print('async'))
layout = QHBoxLayout(self)
layout.addWidget(self.print_sync_button)
layout.addWidget(self.print_async_button)
def handle_print(self, mode='sync'):
worker = Runnable(self, mode)
QThreadPool.globalInstance().start(worker)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())
I described my problems in comments (1., 1a., 2.) in code.
My first problem is that self.page.load(url) never sends a signal loadFinished. I don't know why?
The second problem is more general: how to run async code in QRunnable run() method (if it is at all possible)? For example, I want to generate pdf report with QWebEnginePage (or QWebEngineView.page()) like QWebEnginePage.printToPdf(), but in that case I should use loadFinished signal for QWebEnginePage.load() and pdfPrintingFinished signal for QWebEnginePage.printToPdf(). Those signals will be connected to methods that won't run in QRunnable thread any more. They will run in main thread slowing the gui (those two methods can be slow, not to mention I want to open Adobe Reader with generated pdf document also in thread).
How to accomplish that all that code runs in thread (QRunnable or some other) and not to go back to main thread?
Similar question is here, but it seems it is pending without further discussion.
Upvotes: 0
Views: 444
Reputation: 48424
There are various problems in your question and its code, based on wrong assumptions.
file:report.html
is an acceptable QUrl for local Qt file access, it is not a valid address from the point of view of the browser: if you try to write that path in your web browser, it won't work, even if the file is in the path of the browser executable.QUrl.fromLocalFile(QDir.current().absoluteFilePath('report.html'))
QFileInfo('report.html').absoluteFilePath()
loadFinished
signal doesn't work for setHtml
because that function doesn't "load" anything: it just sets the page content. Loading, in terms of web browser, means both setting the content and finishing its [down]loading;So, the solution is to not use QRunnable, but properly manage printing.
Then, you either create a QWebEnginePage for each document you need to print, or you use a unique QWebEnginePage and queue each loading and printing.
In either case, the provided QUrl must have a proper absolute path.
Obviously, there are differences.
The second option requires very limited resources, but complete queueing obviously means that the process is sequential, so it might take a lot of time.
The first option requires much more system resources (imagine it as having an opened tab for each document, and each web rendering is very CPU/RAM demanding, even if it's not displayed), but has the benefit of faster printing: since loading and printing are asynchronous you can actually process dozens of pages in a very short time interval.
A possible and safer solution is to create a system that limitates the concurrent number of pages and printing processes, and queues the remaining as soon as every printing is completed.
Finally, both QTextDocument and QPrinter are thread safe, so there's no problem in using them in a QRunnable, and they are not synchronous if you properly create a QRunnable that is being executed for each printing (which is one of the main purposes of QRunnable: being able to run it more than once). The only difference is that QTextDocument has a limited HTML support.
Upvotes: 1