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.
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.
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.
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.
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.
Navigating to a URL from QLineEdit
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.
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:
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.
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.
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.