With the release of Qt 5.5 the Qt WebKit API was deprecated and replaced with the new
QtWebEngine API, based on Chromium.
The WebKit API was subsequently removed from Qt entirely with the release of Qt 5.6 in mid-2016.
The change to use Chromium for web widgets within Qt was motivated by improved cross-platform support, multimedia and HTML5 features, together with a rapid development pace offering better future proofing. The benefits were considered enough to warrant breaking API changes in a non-major release, rather than waiting for Qt 6.
The API for
QtWebEngineWidgets was designed to make it — more or less — a drop-in replacement for it's
predecessor, and this is also the case when using the API from PyQt5. However, the opportunity was taken to make a number
of improvements to the API, which require changes to existing code. These changes include simplification of the page model,
and switch of HTML export and printing methods to be asynchronous. This is good news since it prevents
either of these operations from blocking your application execution.
Below is a short walkthrough of the changes, including working examples with the new API.
In the WebKit based API, each page was represented by a
QWebPage object, which in turn contained frames of content.
This reflects the origin of this API during a time when HTML frames were thing, used to break a document down into
grid of constituent parts, each loaded as a separate source. The
.mainFrame() of the page refers to the browser window,
QWebFrame object which can itself contain multiple child frames, or HTML content.
To get the HTML content of the page we would previously have to access it via —
With the use of frames in web pages falling out favour, and being deprecated in HTML5, this API becomes an unnecessary
complication. Now the call to
.page() returns a
QWebEnginePage object, which directly contains the HTML itself.
If you try to use the above to actually get the HTML content of a page, it won't work. That's because this method has been changed to use an asynchronous callback. See below.
Generating the HTML for a page can take some time, particularly on large pages. Not a really long time, but enough to
make your application stutter. To avoid this, the
.toHtml() method on
QWebEnginePage has been converted to be
asynchronous. To receive a copy of the HTML content you call
.toHtml() as before, but now pass in a callback function to
receive the HTML once the generation has completed. The initial completes immediately and so does not block your
The following example shows a save method using a small callback function
write_html_to_file to complete the process
of writing the page HTML to the selected file location.
def save_file(self): filename, _ = QFileDialog.getSaveFileName(self, "Save Page As", "", "Hypertext Markup Language (*.htm *html);;" "All files (*.*)") if filename: def write_html_to_file(html): with open(filename, 'w') as f: f.write(html) self.browser.page().toHtml(write_html_to_file)
write_html_to_file method will be called whenever the generation of the HTML has completed, assuming there
are no errors in doing so. You can pass any Python function/method in as a callback.
Ready to build your own apps?
Then you might enjoy this book! Create Simple GUI Applications with Python & Qt is my guide to building cross-platform GUI applications with Python. Work step by step from displaying your first window to building fully functional desktop software.
In the previous API printing was also handled from the
QWebFrame object, via the print slot (
print_ in PyQt due to
print being a keyword in Python 2.7). This too has been moved to the
QWebEnginePage object, now as a normal
method rather than a slot.
Because PyQt5 is Python 3 only, the method is now named
print(), without a trailing underscore.
To print a page we can call
.print() on any
QWebEnginePage object, passing in an instance of
print to, and a callback function to call once complete.
QPrinter instance can be configured using a
QPrintDialog as normal, however you must ensure the printer
object is not cleaned up/removed before the end of the printing process.
While you can generate a new
QPrinter object from a call to
QPrintDialog, this will be cleared up by Qt
even if you hold onto a Python reference, leading to a segmentation fault. The simplest solution is just to create
QPrinter object at the start of your application and re-use it.
First create an instance of QPrinter on your main window during initialization, e.g.
self.printer = QPrinter()
Then you can use the following to show a print dialog and (optionally) print a page:
def print_page(self): dlg = QPrintDialog(self.printer) if dlg.exec_(): self.browser.page().print(self.printer, self.print_completed) def print_completed(self, success): pass # Do something in here, maybe update the status bar?
The callback is required, but your not required to do anything in it. It receives a
bool value indicating the
success/failure of the printing process.
Print to PDF
QWebEnginePage API also provides a little bonus in the form of PDF printing from HTML pages via
This method accepts either a
str filename to write the resulting PDF to, or a callback function to receive the
rendered PDF as a
browser.page().printToPdf(path) # render to PDF and save to path
Existing files will be overwritten when writing to file. While this operation is is asynchronous there is no callback.
Instead the notification of success/failure comes via a signal on the page object
pdfPrintingFinished, which receives
the file path string and a success
bool. This signal is not triggered when using a callback:
def rendered_pdf_callback(b): print(b) browser.page().printToPdf(rendered_pdf_callback) # render to PDF and send as `QByteArray` to `rendered_pdf_callback`
Paper formats and margins can all be configured by passing additional parameters.