I am writing a small reporting app using wxPython (wxAUI). I want to render my data as HTML, to be displayed in a WebView 'widget'. I am looking for a sample 'hello world' snippet that will show how to display/render an HTML string in a WebView widget - but have been unable to find a single example - and the WebView widget does not seem to be well documented.
Could someone please provide a link to such an example or (better still), post a short snippet here that shows how to use the WebView widget to render an HTML string?
# sample html string to display in WebView widget
html_string = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Hello World!</title>
<script type="text/javascript" src="jquery.js"></script>
<style type="text/css" src="main.css"></style>
</head>
<body>
<span id="foo">The quick brown fox jumped over the lazy dog</span>
<script type="text/javascript">
$(document.ready(function(){
$("span#foo").click(function(){ alert('I was clicked!'); });
});
</script>
</body>
</html>
"""
This is a simple example that works for me.
Make sure you are running the latest version of wxpython. (wxpython 2.9)
import wx
import wx.html2
class MyBrowser(wx.Dialog):
def __init__(self, *args, **kwds):
wx.Dialog.__init__(self, *args, **kwds)
sizer = wx.BoxSizer(wx.VERTICAL)
self.browser = wx.html2.WebView.New(self)
sizer.Add(self.browser, 1, wx.EXPAND, 10)
self.SetSizer(sizer)
self.SetSize((700, 700))
if __name__ == '__main__':
app = wx.App()
dialog = MyBrowser(None, -1)
dialog.browser.LoadURL("http://www.google.com")
dialog.Show()
app.MainLoop()
I posted to this thread after reading the first two entries, and in my post I said something like:
There is an answer here, but it doesn't answer the question. The question was: How do I display an HTML file in a string in a browser
window? The only answer opens the browser
window, but gets data from a url and doesn't use the string contents.
But then I researched the answer further, I took the postings here and came up with the actual answer to the original question, which was: How do I display from a string?:
If you copy the html string assignment into the code sample, but replace the line:
dialog.browser.LoadURL("http://www.google.com")
with:
dialog.browser.SetPage(html_string,"")
Everything should work as desired (displaying html page from a string (instead of url))
Share and Enjoy!
Related
I've seen many many answers about this question, but still cannot figure it out.
I made two files, one - html with qrc definition + new QWebChannel, and second python file with Object and QWebEngineView.
JS script seems to not working. It doesn't binding channel object to local var.
I've tried many examples from many pages, looking native QT language or C++, I cannot figure it out.
QWebChannel definition and qt object are found.
Maybe something is not ok with PySide6? Please help me figure it out.
My code:
index.html
<head>
<title>Test</title>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript">
let dataSource = null
window.onload = function () {
alert('before') //works
new QWebChannel(qt.webChannelTransport, function (channel) {
alert('inside') //not working
dataSource = channel.objects.backend;
}
);
alert('after') //works
alert(dataSource) //null
}
</script>
</head>
<body>
<p>Hello</p>
</body>
</html>
main.py
import sys, os
from PySide6.QtCore import Signal, QUrl, Slot, QObject
from PySide6.QtWidgets import QMainWindow, QApplication, QVBoxLayout, QWidget
from PySide6.QtWebEngineWidgets import *
from PySide6.QtWebEngineCore import *
from PySide6.QtWebChannel import QWebChannel
class Backend(QObject):
#Slot(result=int)
def getValue(self):
return 1
#Slot(int)
def printValue(self, val):
print(val)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow,self).__init__(*args, **kwargs)
self.browser = QWebEngineView()
backend = Backend()
channel = QWebChannel()
channel.registerObject("backend", backend)
self.browser.page().setWebChannel(channel)
current_dir = os.path.dirname(os.path.realpath(__file__))
filename = os.path.join(current_dir, "index.html")
url = QUrl.fromLocalFile(filename)
self.browser.load(url)
self.setCentralWidget(self.browser)
self.resize(1200,900)
self.show()
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
UPDATE:
I extended view, added dev tool and jquery to check objects - that what I have:
UPDATE 2:
I removed PySide6, installed PyQt5 and used example from here:
How to receive data from python to js using QWebChannel?
Everything works :/
I spend whole day to figure it out. I don't know why it's not working.
I'm building a browser using PyQt5. It's a rather huge code, but this is the main problem I'm facing. The code is this:
import sys
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QMainWindow
app = QApplication(sys.argv)
web = QWebEngineView()
file = open("example.html", "r")
html = file.read()
web.setHtml(html)
file.close()
web.show()
sys.exit(app.exec_())
The problem is that the rendering is rather strange.Image of the rendering is here.
The contents of the example.html file:
<!DOCTYPE html>
<head><title>JS Example</title></head>
<h1>JS example</h1>
<p><button type = 'button' onclick = "document.getElementById('tobeshown').style.display='block'">Show hidden parts of this page</button></p>
<p id = 'tobeshown' style = "display:none">
Peekaboo!
</p>
<p>
<button type = 'button' onclick="document.getElementById('tobeshown').style.display='none'">Hide it!</button>
</p>
</body>
</html>
The expected output(this is in Mozilla Firefox browser): here
Can anyone tell me why the PyQt5 rendering engine produces those symbols at the top? And what can I do to resolve it? Thank you very much.
So the problem is not WebEngine's rendering but instead Python's reading of a file. Changing the file's encoding to UTF-8 solved the problem for me.
I am trying to display a video file inside a PySide2 app I'm writing. That app uses a QWebEngineView and loads a local html file that contains a <video> tag, pointing to a local mov or mp4 file. Somehow I can see the player, but it doesn't load the file.
I've put this little html test page together which loads fine in Chrome, but not in my app. Note that in this file I'm using an online mp4 file, but I've tried both, local and online files, mov and mp4.
<!doctype html>
<html lang="en">
<head>
</head>
<body>
<video width="320" height="240" controls>
<source src="https://archive.org/download/VideoTestFiles/1280X72025FpsPhotoJpeg75.mp4" type="video/mp4" >
</video>
</body>
In my PySide2 app, I'm loading the page like follows:
import os, sys
from PySide2 import QtWidgets
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
from PySide2.QtCore import QUrl
app = QtWidgets.QApplication(sys.argv)
view = QWebEngineView()
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test.html")
view.load(QUrl.fromLocalFile(path))
view.show()
sys.exit(app.exec_())
I have also tried the following settings, none of which seem to help:
view.settings().setAttribute(QWebEngineSettings.PluginsEnabled, True)
view.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)
view.settings().setAttribute(QWebEngineSettings.AllowRunningInsecureContent, True)
view.settings().setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls, True)
view.settings().setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
Any help appreciated!
Edit:
I just realized: while there is no error in Chrome console, there is an error being displayed in the Python console after closing the app:
[9476:13164:1017/182854.157:ERROR:media_internals.cc(102)] Cannot get RenderProcessHost
I'm working on an asset management app for our company. It's a standalone Python3 app using PySide2 and talking to our database backend. One of the views I'm writing is supposed to be a HTML5-style responsive gallery: assets are displayed as thumbnails, on mouse-over they display extra information, and on click they initiate an action (eg opening the asset in the appropriate app).
What's the best way to implement this in PySide2/PyQt5?
Since I'd feel comfortable implementing and styling something like this in HTML5, I'm inclined to do it with QWebEngineView and dynamically generate HTML and CSS in python, then use QWebEngineView.setHtml() to display it.
Is this a good way to do it inside a PySide2 app, that doesn't use HTML otherwise? Are there more Qt-ish ways to achieve a dynamic, responsive, style-able gallery?
If I would use QWebEngineView, how would I intercept the user clicking on one of the HTML elements? I found this question, which sounds like it could be a solution for this: Capture server response with QWebEngineView . Is there a simpler solution?
Qt offers many alternatives for what you want (They are not complete solutions since you do not clearly indicate what you require):
Qt Widgets: PySide2 composite widget Hover Effect,
Qt QML: PySide2/QML populate and animate Gridview model/delegate,
Or QWebEngineView, which focuses my current answer.
The implementation of the mouse-over effect will not be implemented because I am not an expert in frontend, but I will focus on communication between the parties.
To communicate Python information to JS, you can do it with the runJavaScript() method of QWebEnginePage and/or with QWebChannel, and the reverse part with QWebChannel (I don't rule out the idea that QWebEngineUrlRequestInterceptor could be an alternative solution but in this case the previous solutions they are simpler). So in this case I will use QWebChannel.
The idea is to register a QObject that sends the information through signals (in this case JSON), by the side of javascript parsing the JSON and creating dynamic HTML, then before any event such as the click call a slot of the QObject.
Considering the above, the solution is:
├── index.html
├── index.js
└── main.py
import json
from PySide2 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, QtWebChannel
class GalleryManager(QtCore.QObject):
dataChanged = QtCore.Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._data = []
self._is_loaded = False
#QtCore.Slot(str)
def make_action(self, identifier):
print(identifier)
#QtCore.Slot()
def initialize(self):
self._is_loaded = True
self.send_data()
def send_data(self):
if self._is_loaded:
self.dataChanged.emit(json.dumps(self._data))
#property
def data(self):
return self._data
#data.setter
def data(self, d):
self._data = d
self.send_data()
if __name__ == "__main__":
import os
import sys
# sys.argv.append("--remote-debugging-port=8000")
app = QtWidgets.QApplication(sys.argv)
current_dir = os.path.dirname(os.path.realpath(__file__))
view = QtWebEngineWidgets.QWebEngineView()
channel = QtWebChannel.QWebChannel(view)
gallery_manager = GalleryManager(view)
channel.registerObject("gallery_manager", gallery_manager)
view.page().setWebChannel(channel)
def on_load_finished(ok):
if not ok:
return
data = []
for i, path in enumerate(
(
"https://source.unsplash.com/pWkk7iiCoDM/400x300",
"https://source.unsplash.com/aob0ukAYfuI/400x300",
"https://source.unsplash.com/EUfxH-pze7s/400x300",
"https://source.unsplash.com/M185_qYH8vg/400x300",
"https://source.unsplash.com/sesveuG_rNo/400x300",
"https://source.unsplash.com/AvhMzHwiE_0/400x300",
"https://source.unsplash.com/2gYsZUmockw/400x300",
"https://source.unsplash.com/EMSDtjVHdQ8/400x300",
"https://source.unsplash.com/8mUEy0ABdNE/400x300",
"https://source.unsplash.com/G9Rfc1qccH4/400x300",
"https://source.unsplash.com/aJeH0KcFkuc/400x300",
"https://source.unsplash.com/p2TQ-3Bh3Oo/400x300",
)
):
d = {"url": path, "identifier": "id-{}".format(i)}
data.append(d)
gallery_manager.data = data
view.loadFinished.connect(on_load_finished)
filename = os.path.join(current_dir, "index.html")
view.load(QtCore.QUrl.fromLocalFile(filename))
view.resize(640, 480)
view.show()
sys.exit(app.exec_())
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="index.js"> </script>
</head>
<body>
<div class="container">
<h1 class="font-weight-light text-center text-lg-left mt-4 mb-0">Thumbnail Gallery</h1>
<hr class="mt-2 mb-5">
<div id="container" class="row text-center text-lg-left">
</div>
</div>
</body>
</html>
var gallery_manager = null;
new QWebChannel(qt.webChannelTransport, function (channel) {
gallery_manager = channel.objects.gallery_manager;
gallery_manager.dataChanged.connect(populate_gallery);
gallery_manager.initialize();
});
function populate_gallery(data) {
const container = document.getElementById('container');
// clear
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// parse json
var d = JSON.parse(data);
// fill data
for (const e of d) {
var identifier = e["identifier"];
var url = e["url"];
var div_element = create_div(identifier, url)
container.appendChild(div_element);
}
}
function create_div(identifier, url){
var html = `
<div class="d-block mb-4 h-100">
<img class="img-fluid img-thumbnail" src="${url}" alt="">
</div>
`
var div_element = document.createElement("div");
div_element.className = "col-lg-3 col-md-4 col-6"
div_element.innerHTML = html;
div_element.addEventListener('click', function (event) {
gallery_manager.make_action(identifier);
});
return div_element;
}
Loving the answer from #eyllanesc using QWebChannel! I had not come across this and hadn't dared to dream about having AJAX-style communication between a PySide app and a WebView! Loving it!
Here's a less flexible/elaborate alternative I had come up with meanwhile, using QWebEnginePage.acceptNavigationRequest(). I much prefer the QWebChannel answer, but others might find this option helpfull as well.
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
class MyPage(QWebEnginePage):
def acceptNavigationRequest(self, url, type, isMainFrame):
print(url, type, isMainFrame)
if url.toString().startswith('app://'):
print('intercepted click, do stuff')
return False
return True
def createHtml():
html = """
<html>
<head>
<style>
.item {
position: relative;
}
.animgif {
display: none;
position: absolute;
top: 0;
left: 0;
}
.item:hover .animgif {
display: block;
}
</style>
</head>
<body>
<a href="app://action/click?id=1234">
<div class="item">
<img class="thumb" src="file://server01/path/to/thumbnail.png">
<img class="animgif" src="file://server/path/to/thumbnail.gif">
</div>
</a>
</body>
</html>
"""
return html
if __name__ == '__main__':
import sys
from PySide2 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
page = MyPage()
view = QWebEngineView()
view.setPage(page)
html = createHtml()
baseUrl = "file://server01/"
page.setHtml(html, baseUrl=baseUrl)
view.show()
sys.exit(app.exec_())
The idea is to create html dynamically and use page.setHtml(html) to load it on the view. In this example the createHtml() function is rudimentary, but shows the intention. Subclassing QWebEnginePage allows you to override acceptNavigationRequest(), which allows you to intercept clicks and decide what to do with them. In this example I chose to use and detect a protocol 'app://' and act on in accordingly.
One more note is that in our case, all files/images/ect live on the local file system. To avoid a cross-origin security exception, I had to provide baseUrl in setHtml() and set it to the same file path that the files are hosted on.
html = createHtml()
baseUrl = "file://server01/"
page.setHtml(html, baseUrl=baseUrl)
Python 3.6 PYQT 5.12.1
I am ready to show the style I need by pyqt5 and I knew that the QTextEdit in pyqt5 can display the html code pretty good (I have some experience in web development), so I decided to use html/css to show my style . However , it may have some problem in showing the code in css . What can I do to let it can show the css/javascript ? If it can‘t , can recommend other methods to modify the style?
It can show some style like width = "100" height = "100" when I code it in the html but not css and some can't display like border-radius:50%;. It won't get any effect when I code the style in css . By the way , I've imported CSS code.
The CSS code do nothing in QTextEdit (but it is ok in html)
.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class WINDOW(QMainWindow):
def __init__(self):
super().__init__()
self.init()
def init(self):
w_width,w_height,w_x,w_y = 700,640,700,200
self.set_size_pos(w_width,w_height,w_x,w_y);
self.set_message_textedit()
self.message_textedit.setHtml(self.get_html())
self.show()
def set_size_pos(self,width,height,x,y):
'''
set window's width , height and position
'''
self.resize(width,height)
self.move(x,y)
def set_message_textedit(self):
self.message_textedit = QTextEdit(self)
self.message_textedit.setFont(QFont("Microsoft YaHei",12))
self.message_textedit.resize(680,420)
self.message_textedit.move(10,50)
self.message_textedit.setReadOnly(True)
def get_html(self):
html = ""
with open("./chat-style/chat.html","r",encoding = "utf-8") as f:
html = f.read()
return html
if __name__ == '__main__':
app = QApplication(sys.argv)
test = WINDOW()
sys.exit(app.exec_())
.html
<!doctype html>
<html lange="zh-CN">
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="./chat.css">
<script src = "chat.js"></script>
</head>
<body>
<img class = "tx" src="E:\study\assiataant\picture\icon.jpg">
<div>Welcome ~ Don!</div>
</body>
</html>
.css
.tx
{
border-radius:50%;
width: 30;
height: 30;
}
QTextEdit only supports CSS 2.1 as indicated by the docs:
All CSS 2.1 selector classes are supported except pseudo-class selectors such as :first-child, :visited and :hover.
But border-radius was introduced in CSS3. So you can not use it unfortunately. I recommend you read the following link so that you know the allowed tags.
Another alternative is to use QWebEngineView that supports these tags:
*.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os
import sys
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
view = QtWebEngineWidgets.QWebEngineView()
file = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"chat-style/chat.html"
)
view.load(QtCore.QUrl.fromLocalFile(file))
self.setCentralWidget(view)
self.resize(640, 480)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
If you do not have QWebEngineView installed, you must install it with the following command:
python -m pip install PyQtWebEngine