I am currently looking into a way to create GUI desktop applications with Python and HTML/CSS/JS using PyQt5's QWebEngineView.
In my little demo application, I use a QWebChannel to publish a Python QObject to the JavaScript side, so that data can be shared and passed back and forth. Sharing and connecting slots and signals so far works fine.
I'm having difficulties though with the synchronisation of simple (property) values. From what I've read, the way to go is to implement a pyqtProperty in the shared QObject via decorated getter and setter functions, with an additional signal emitted in the setter, used to notify JavaScript when the value has changed. The code below shows that and so far this works fine:
import sys
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
class HelloWorldHtmlApp(QWebEngineView):
html = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
var backend;
new QWebChannel(qt.webChannelTransport, function (channel) {
backend = channel.objects.backend;
});
</script>
</head>
<body> <h2>HTML loaded.</h2> </body>
</html>
'''
def __init__(self):
super().__init__()
# setup a page with my html
my_page = QWebEnginePage(self)
my_page.setHtml(self.html)
self.setPage(my_page)
# setup channel
self.channel = QWebChannel()
self.backend = self.Backend(self)
self.channel.registerObject('backend', self.backend)
self.page().setWebChannel(self.channel)
class Backend(QObject):
""" Container for stuff visible to the JavaScript side. """
foo_changed = pyqtSignal(str)
def __init__(self, htmlapp):
super().__init__()
self.htmlapp = htmlapp
self._foo = "Hello World"
@pyqtSlot()
def debug(self):
self.foo = "I modified foo!"
@pyqtProperty(str, notify=foo_changed)
def foo(self):
return self._foo
@foo.setter
def foo(self, new_foo):
self._foo = new_foo
self.foo_changed.emit(new_foo)
if __name__ == "__main__":
app = QApplication.instance() or QApplication(sys.argv)
view = HelloWorldHtmlApp()
view.show()
app.exec_()
Starting this with the Debugger connected, I can call the backend.debug()
slot in the JavaScript console, which leads to the value of backend.foo
being "I modified foo!" afterwards, which means the Python code succesfully changed the JavaScript variable.
This is kind of tedious though. For every value I'd want to share, I'd have to
- create an internal variable (here
self._foo
) - create a getter function
- create a setter function
- create a signal in the
QObject
's body - emit this signal explicitly in the setter function
Is there any simpler way to achieve this? Ideally some sort of one-liner declaration? Maybe using a class or function to pack that all up? How would I have to bind this to the QObject
later on? I'm thinking of something like
# in __init__
self.foo = SyncedProperty(str)
Is this possible? Thanks for your ideas!