Search code examples
pythonpython-3.xpyqtpyqt5qtwebchannel

QWebEnginePage interactive with javascript not working?


I am not familiar with javascript and QWebEnginePage.
When i set self.content.setText('text') content, the QWebEngineView content not changed?

The python code

class Document(QObject):
    textChanged = pyqtSignal(str)
    def __init__(self):
        super().__init__()
        self.text  = ''
    
    def setText(self, text):
        self.text = text
        self.textChanged.emit(text)
        print('emit')

class Demo(QWebEngineView):
    def __init__(self):
        super().__init__()
        self.content = Document()
        page = self.page()
        channel = QWebChannel()
        channel.registerObject('content', self.content)
        page.setWebChannel(channel)
        with open('index.html') as f:
            self.setHtml(f.read())

        self.content.setText('text')

app = QApplication([])
demo = Demo()
demo.resize(500, 400)
demo.show()
app.exec()

The index html:

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<head>
    <script src="qwebchannel.js"></script>
</head>
<body>
<div id="placeholder">22</div>
<script>
    'use strict';

    var placeholder = document.getElementById('placeholder');

    var updateText = function (text) {
        placeholder.innerHTML = text;
        console.log(text);
    }

    new QWebChannel(qt.webChannelTransport,
        function (channel) {
            var content = channel.objects.content;
            updateText(content.text);
            content.textChanged.connect(updateText);
        }
    );
</script>
</body>
</html>


Solution

  • You have the following errors:

    • channel is a local variable that will be removed as soon as the "Demo" constructor is finished, and that is the intermediary between Python and Javascript communication. The solution is to extend the life cycle by passing it a parent (Qt style) or make it an attribute of the class.

    • In the .html you are trying to include qwebchannel.js but in general you should use <script src="qrc:///qtwebchannel/qwebchannel.js"></script>.

    • If you export a QObject to javascript then only the Q-Properties, QSlot and QSignals will be exported since QWebChannel uses the QMetaObject instropection, but "text" is neither of them so it will be undefined in javascript. The solution is to expose it as a pyqtProperty.

    import os
    from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QUrl
    from PyQt5.QtWidgets import QApplication
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    from PyQt5.QtWebChannel import QWebChannel
    
    
    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
    
    class Document(QObject):
        textChanged = pyqtSignal(str)
    
        def __init__(self):
            super().__init__()
            self._text = ""
    
        def text(self):
            return self._text
    
        def setText(self, text):
            self._text = text
            self.textChanged.emit(text)
            print("emit")
    
        text = pyqtProperty(str, fget=text, fset=setText, notify=textChanged)
    
    
    class Demo(QWebEngineView):
        def __init__(self):
            super().__init__()
    
            self.content = Document()
    
            channel = QWebChannel(self)
            channel.registerObject("content", self.content)
            self.page().setWebChannel(channel)
    
            filename = os.path.join(CURRENT_DIR, "index.html")
            self.load(QUrl.fromLocalFile(filename))
    
            self.content.setText("text")
    
    
    def main():
        app = QApplication([])
        demo = Demo()
        demo.resize(500, 400)
        demo.show()
        app.exec()
    
    
    if __name__ == "__main__":
        main()
    
    <!doctype html>
    <html lang="en">
    <meta charset="utf-8">
    <head>
        <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    </head>
    <body>
    <div id="placeholder">22</div>
    <script>
        'use strict';
    
        var placeholder = document.getElementById('placeholder');
    
        var updateText = function (text) {
            placeholder.innerHTML = text;
            console.log(text);
        }
    
        new QWebChannel(qt.webChannelTransport,
            function (channel) {
                var content = channel.objects.content;
                updateText(content.text);
                content.textChanged.connect(updateText);
            }
        );
    </script>
    </body>
    </html>