Search code examples
pythonpython-2.7pyside2qwebengineview

How can I get the maximum and current value of the verticalScrollBar when displaying html with QWebEngineView


I'm attempting to create a UI in Maya (Python 2.7.11 and Maya Qt 5.6.1. and PySide2) where the OK button is grayed out until the user scrolls to the bottom. I can do this easily with QTextEdit by grabbing the value and maximum from the verticalScrollBar but I can't seem to find similar objects in QWebEngineView or QWebEnginePage. Any Ideas?


Solution

  • In the case you want to get some information from the DOM you have to do it with javascript, in this case we will create an object that will have the required information and export it to the HTML so that each time the scroll status changes it will be updated.

    from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
    from jinja2 import Template
    
    class Element(QtCore.QObject):
        def __init__(self, name, parent=None):
            super(Element, self).__init__(parent)
            self._name = name
    
        @property
        def name(self):
            return self._name
    
        def script(self):
            return ''
    
    class YScrollBarListener(Element):
        valueChanged = QtCore.Signal(int)
        maximumChanged = QtCore.Signal(int)
    
        def __init__(self, name, parent=None):
            super(YScrollBarListener, self).__init__(name, parent)
            self._value = -1
            self._maximum = -1
    
        @QtCore.Property(int, notify=valueChanged)
        def value(self):
            return self._value
    
        @QtCore.Property(int, notify=maximumChanged)
        def maximum(self):
            return self._maximum
    
        def script(self):
            _script = '''
            window.addEventListener("scroll", function(event){
                {{name}}.update_value(this.scrollY);
                // https://stackoverflow.com/a/43571388/6622587
                var scrollMaxY = window.scrollMaxY || (document.documentElement.scrollHeight - document.documentElement.clientHeight)
                {{name}}.update_maximum(scrollMaxY)
            });
            '''
            return Template(_script).render(name=self.name)
    
        @QtCore.Slot(int)
        def update_value(self, value):
            if self._value != value:
                self._value = value
                self.valueChanged.emit(value)
    
        @QtCore.Slot(int)
        def update_maximum(self, maximum):
            if self._maximum != maximum:
                self._maximum = maximum
                self.maximumChanged.emit(maximum)
    
    class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
        def __init__(self, *args, **kwargs):
            super(WebEnginePage, self).__init__(*args, **kwargs)
            self.loadFinished.connect(self.onLoadFinished)
            self._objects = []
    
        def add_object(self, obj):
            self._objects.append(obj)
    
        @QtCore.Slot(bool)
        def onLoadFinished(self, ok):
            print("Finished loading: ", ok)
            if ok:
                self.load_qwebchannel()
                self.add_objects()
    
        def load_qwebchannel(self):
            file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
            if file.open(QtCore.QIODevice.ReadOnly):
                content = file.readAll()
                file.close()
                self.runJavaScript(content.data().decode())
            if self.webChannel() is None:
                channel = QtWebChannel.QWebChannel(self)
                self.setWebChannel(channel)
    
        def add_objects(self):
            if self.webChannel() is not None:
                objects = {obj.name : obj for obj in self._objects}
                self.webChannel().registerObjects(objects)
                _script = '''
                {% for obj in objects %}
                var {{obj}};
                {% endfor %}
                new QWebChannel(qt.webChannelTransport, function (channel) {
                {% for obj in objects %}
                    {{obj}} = channel.objects.{{obj}};
                {% endfor %}
                }); 
                '''
                self.runJavaScript(Template(_script).render(objects=objects.keys()))
                for obj in self._objects:
                    if isinstance(obj, Element):
                        self.runJavaScript(obj.script())
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
    
            self._scrollbar_listener = YScrollBarListener("y_scrollbar_listener", self)
            self._scrollbar_listener.valueChanged.connect(self.on_y_value_changed)
            view = QtWebEngineWidgets.QWebEngineView()
            page = WebEnginePage(view)
            page.add_object(self._scrollbar_listener)
            view.setPage(page)
    
            view.load(QtCore.QUrl("https://stackoverflow.com/questions/43282899"))
    
            self._button = QtWidgets.QPushButton("button")
            progressbar = QtWidgets.QProgressBar(maximum=100)
            view.loadProgress.connect(progressbar.setValue)
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(view)
            lay.addWidget(self._button)
            lay.addWidget(progressbar)
    
            self.resize(640, 480)
    
        def on_y_value_changed(self, value):
            self._button.setEnabled(self._scrollbar_listener.maximum != value)
    
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle('fusion')
        app.setStyleSheet('''
        QPushButton
        {
            background-color: #2E8B57;
        }
        QPushButton:disabled
        {
            background-color: #FFFAFA;
        }
        ''')
        w = Widget()
        w.show()
        sys.exit(app.exec_())