Adding navigational controls to a PyQt5 Web Browser

Hook up QAction signals to web browser slots

PyQt5 Tutorial Build a Web Browser with PyQt5

Heads up! You've already completed this tutorial.

In the first part of this tutorial we put together a simple skeleton of a web browser using PyQt5's built-in QWebEngineView 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 add navigation controls using PyQt5. These are added as a series of QAction objects on a QToolBar. We add these definitions to the __init__ block of the QMainWindow.

Adding a Navigation Toolbar with QToolBar

The first step in building browser navigation is creating a QToolBar and adding a Back button that connects to QWebEngineView's built-in back slot.

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

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 )
navtb.addAction(back_btn)

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

Connecting Forward, Reload and Home Buttons

Next, we add the Forward, Reload, and Home buttons to the toolbar. The forward and reload actions use built-in QWebEngineView slots, while the home button requires a custom slot function.

python
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 )
navtb.addAction(next_btn)

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 )
navtb.addAction(reload_btn)

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

While forward, back and reload can use built-in QWebEngineView 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.

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

Adding a URL Bar and Stop Button

Any decent web browser also needs a URL bar and a way to stop navigation. We'll add a QLineEdit as the address bar, an SSL indicator icon using QLabel, and a stop button connected to QWebEngineView.stop.

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

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

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 )
navtb.addAction(stop_btn)

As before the 'stop' functionality is available as a slot on 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.

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PySide6 Edition) The hands-on guide to making apps with Python — Save time and build better with this book. Over 15K copies sold.

Get the book

When the user types a URL into the QLineEdit and presses Enter, we need to convert the text into a QUrl object. If no scheme (like http or https) is provided, we default to http.

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

    self.browser.setUrl(q)

Updating the URL Bar When the Page Changes

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 QWebEngineView. We set up the connections from the signals in the __init__ block as follows:

python
self.browser.urlChanged.connect(self.update_urlbar)
self.browser.loadFinished.connect(self.update_title)

Then we define the target slot functions 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.

python
def update_urlbar(self, q):

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

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

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

Updating the Window Title Dynamically

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.

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

Summary

That's the basic browser navigation interface implemented with PyQt5. We've added a QToolBar with back, forward, reload, home, and stop buttons using QAction, along with a URL bar built from QLineEdit and an SSL indicator. The QWebEngineView widget provides built-in slots for most navigation actions, making it straightforward to connect toolbar buttons to browser functionality.

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.

PyQt/PySide Development Services — Stuck in development hell? I'll help you get your project focused, finished and released. Benefit from years of practical experience releasing software with Python.

Find out More

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

Adding navigational controls to a PyQt5 Web Browser was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.