[[ activeDiscount.description ]] I'm giving a [[ activeDiscount.discount ]]% discount on my books with the code [[ couponCode ]].

The Qt6 editions of my books are now available, supporting Python 3 with PyQt6 and PySide6.

Read the free tutorial below or unlock the video

Unlock video course
This video is unlocked by your membership.

Adding navigational controls to a PyQt5 Web Browser
Hook up QAction signals to web browser slots

In the first part of this tutorial we put together a simple skeleton of a browser using Qt's built-in browser widget. This allows you to open a webpage, view it and click around -- all that is handled automatically for you. But the interface is entirely up to you.

To convert this bare-bones application into something more resembling an actual browser, in this part we'll set about adding UI controls. These are added as a series of QActions on a QToolbar. We add these definitions to the __init__ block of the QMainWindow.

navtb = QToolBar("Navigation")
navtb.setIconSize( QSize(16,16) )

back_btn = QAction( QIcon(os.path.join('icons','arrow-180.png')), "Back", self)
back_btn.setStatusTip("Back to previous page")
back_btn.triggered.connect( self.browser.back )

The QWebEngineView includes slots for forward, back and reload navigation, which we can connect to directly to our action's .triggered signals.

next_btn = QAction( QIcon(os.path.join('icons','arrow-000.png')), "Forward", self)
next_btn.setStatusTip("Forward to next page")
next_btn.triggered.connect( self.browser.forward )

reload_btn = QAction( QIcon(os.path.join('icons','arrow-circle-315.png')), "Reload", self)
reload_btn.setStatusTip("Reload page")
reload_btn.triggered.connect( self.browser.reload )

home_btn = QAction( QIcon(os.path.join('icons','home.png')), "Home", self)
home_btn.setStatusTip("Go home")
home_btn.triggered.connect( self.navigate_home )

While forward, back and reload can use built-in slots to perform their actions, the navigate home button requires a custom slot function. The slot function is defined on our QMainWindow class, and simply sets the URL of the browser to the Google homepage. Note that the URL must be passed as a QUrl object.

def navigate_home(self):
    self.browser.setUrl( QUrl("http://www.google.com") )

Any decent web browser also needs an URL bar, and some way to stop the navigation.

self.httpsicon = QLabel() # Yes, really!
self.httpsicon.setPixmap( QPixmap( os.path.join('icons','lock-nossl.png') ) )

self.urlbar = QLineEdit()
self.urlbar.returnPressed.connect( self.navigate_to_url )

stop_btn = QAction( QIcon(os.path.join('icons','cross-circle.png')), "Stop", self)
stop_btn.setStatusTip("Stop loading current page")
stop_btn.triggered.connect( self.browser.stop )

As before the 'stop' functionality is available as a slot on the QWebEngineView itself, and we can simply connect the .triggered signal from the stop button to the existing slot. However, other features of the URL bar we must handle independently.

First we add a QLabel to hold our SSL or non-SSL icon to indicate whether the page is secure. Next, we add the URL bar which is simply a QLineEdit. To trigger the loading of the URL in the bar when entered (return key pressed) we connect to the .returnPressed signal on the widget to drive a custom slot function to trigger navigation to the specified URL.

def navigate_to_url(self): # Does not receive the Url
    q = QUrl( self.urlbar.text() )
    if q.scheme() == "":


We also want the URL bar to update in response to page changes. To do this we can use the .urlChanged and .loadFinished signals from the QWebEngineView. We set up the connections from the signals in the __init__ block as follows:


Then we define the target slot functions which for these signals. The first, to update the URL bar accepts a QUrl object and determines whether this is a http or https URL, using this to set the SSL icon.

This is a terrible way to test if a connection is 'secure'. To be correct we should perform a certificate validation.

The QUrl is converted to a string and the URL bar is updated with the value. Note that we also set the cursor position back to the beginning of the line to prevent the QLineEdit widget scrolling to the end.

def update_urlbar(self, q):

    if q.scheme() == 'https':
        # Secure padlock icon
        self.httpsicon.setPixmap( QPixmap( os.path.join('icons','lock-ssl.png') ) )

        # Insecure padlock icon
        self.httpsicon.setPixmap( QPixmap( os.path.join('icons','lock-nossl.png') ) )

    self.urlbar.setText( q.toString() )

It's also a nice touch to update the title of the application window with the title of the current page. We can get this via browser.page().title() which returns the contents of the <title></title> tag in the currently loaded web page.

def update_title(self):
    title = self.browser.page().title()
    self.setWindowTitle("%s - Mozarella Ashbadger" % title)

That's the basic interface implemented. In the next part we'll look at adding the standard Load & Save operations, to allow us to open local HTML files and save web pages to disk.

Continue reading

QtWebEngineWidgets, the new browser API in PyQt 5.6  web

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 … More