PySidePyQt Tutorial QWebView

The QWebView is a highly useful control; it allows you to display web pages from URLs, arbitrary HTML, XML with XSLT stylesheets, web pages constructed as QWebPages, and other data whose MIME types it knows how to interpret. It uses the WebKit web browser engine. WebKit is an up-to-date, standards-compliant rendering engine used by Google’s Chrome, Apple’s Safari, and soon the Opera browser.

Creating and Filling a QWebView

Content from a QWebView’s URL

You instantiate a QWebView like any othe QWidget, with an optional parent. There are a number of ways to put content into it, however. The simplest — and possibly the most obvious — is its load method, which takes a QUrl; the simplest way to construct a QUrl is with a unicode URL string:

web_view.load(QUrl('http://www.www.pythoncentral.io'))

That loads Python Central’s main page in the QWebView control. Equivalent would be to use the setUrl method, like so:

web_view.setUrl(QUrl('http://www.www.pythoncentral.io'))

Load Arbitrary HTML with QWebView

There are other interesting ways to load content into a QWebView. You can load generated HTML into it using the setHtml method. For example, you can do something like this:

html = '''<html>
<head>
<title>A Sample Page</title>
</head>
<body>
<h1>Hello, World!</h1>
<hr />
I have nothing to say.
</body>
</html>'''
 
web_view.setHtml(html)

The setHtml method can take an optional second argument, the base URL of the document – the URL based on which any relative links contained in the document are resolved.

Other QWebView Content Types

QWebView‘s content does not have to be HTML; if you have other browser-viewable content, you can put it in a QWebView using its setContent method, which accepts the content and optionally its MIME type and a base URL. If the MIME type is omitted, it will be assumed that it is text/html; no autodetection of MIME types has been implemented yet, though it is at least tentatively planned. I’ve not managed to find a list of MIME types that can be handled by the QWebView, but here is an example that works with a PNG file:

app = QApplication([])
win = QWebView()
 
img = open('myImage.png', 'rb').read()
win.setContent(img, 'image/png')
 
win.show()
app.exec_()

Interacting QWebView with JavaScript

Presenting a user with web-style content is useful in itself, but that content can be made interactive with JavaScript, which can be initiated from Python code.

QWebView contains a QWebFrame object, which is useful to us right now for its evaluateJavaScript method. That method accepts a string of JavaScript, evaluates it in the context of the QWebView‘s content, and returns its value.

What values can be returned? The PySide documentation for QWebFrame, like that for PyQt and Qt itself, is not clear on that point. In fact, that information doesn’t appear to be available on the web at all, so I did some testing.

It appears that strings and booleans just plain work, and numbers, objects, undefined, and null work with caveats:

  • Because JavaScript lacks separate integer and floating-point datatypes, numbers are consistently converted to Python floats.
  • Objects are returned as Python dictionaries unless they are functions or arrays; functions are returned as useless empty dictionaries, and arrays become Python lists.
  • undefined becomes None, sensibly enough.
  • null becomes, less sensibly, ”. That’s right — an empty string.

Note especially the behavior regarding null and functions, as both can cause code that looks right to behave wrong. I see no better option for functions, but null is especially confusing; the only way to detect a null value from evaluateJavaScript is to do the comparison val === null on the JavaScript side before you return it to Python. (It is at this point that we collectively grieve over JavaScript’s ill-thought-out types.)

An important caution about evaluateJavaScript: it has all the security implications of JavaScript’s built-in eval, and should be used with the discretion that is so seldom displayed by front-end JavaScript coders. It would be far too simple, for example, to allow the execution of arbitrary JavaScript by naïvely building a string and sending it to evaluateJavaScript. Be careful, validate user input, and block anything that looks too clever.

Example of Evaluating JavaScript in a QWebView

Now, let’s throw caution to the wind and look at a simple example. It will show a form that allows the user to enter a first and last name. There will be a full name entry that is disabled; the user cannot edit it. There is a submit button, but it is hidden. (Don’t do this in real life, okay? This form is a usability and cultural-sensitivity disaster, and would be almost insultingly dumb to show in public.) We’ll supply a Qt button that fills out the full name entry, shows the submit button, and prints the full name to the console. Here’s the source of the example:

# Create an application
app = QApplication([])
 
# And a window
win = QWidget()
win.setWindowTitle('QWebView Interactive Demo')
 
# And give it a layout
layout = QVBoxLayout()
win.setLayout(layout)
 
# Create and fill a QWebView
view = QWebView()
view.setHtml('''
  <html>
    <head>
      <title>A Demo Page</title>
 
      <script language="javascript">
        // Completes the full-name control and
        // shows the submit button
        function completeAndReturnName() {
          var fname = document.getElementById('fname').value;
          var lname = document.getElementById('lname').value;
          var full = fname + ' ' + lname;
 
          document.getElementById('fullname').value = full;
          document.getElementById('submit-btn').style.display = 'block';
 
          return full;
        }
      </script>
    </head>
 
    <body>
      <form>
        <label for="fname">First name:</label>
        <input type="text" name="fname" id="fname"></input>
        <br />
        <label for="lname">Last name:</label>
        <input type="text" name="lname" id="lname"></input>
        <br />
        <label for="fullname">Full name:</label>
        <input disabled type="text" name="fullname" id="fullname"></input>
        <br />
        <input style="display: none;" type="submit" id="submit-btn"></input>
      </form>
    </body>
  </html>
''')
 
# A button to call our JavaScript
button = QPushButton('Set Full Name')
 
# Interact with the HTML page by calling the completeAndReturnName
# function; print its return value to the console
def complete_name():
    frame = view.page().mainFrame()
    print frame.evaluateJavaScript('completeAndReturnName();')
 
# Connect 'complete_name' to the button's 'clicked' signal
button.clicked.connect(complete_name)
 
# Add the QWebView and button to the layout
layout.addWidget(view)
layout.addWidget(button)
 
# Show the window and run the app
win.show()
app.exec_()

Try running it. Fill out a first and last name and click the button. You should see a full name and a submit button appear. Looking at the console, you should see the full name printed there as well.

This was a very heavily contrived example, but we can do much more interesting things: in the next installment, we’ll build a simple application that combines HTML in a QWebView and some other Qt widgets to usefully work with a web API.

Leave a Reply

Your email address will not be published. Required fields are marked *