PyQt5: mouseClick and source-code in QWebEngineView
Asked Answered
G

1

5

I have a working script that uses PyQt-5.5.1, which I now want to port to a new PyQt version (5.7). Adapting most of the things was fine, but I faced two major problems: (1) to perform a (simulated) mouseclick, (2) to access (let's say: print) the html source-code of a webpage which is currently displayed in the QWebView or QWebEngineView, respectively.

For example, I could do the following using QWebView in PyQt-5.5.1:

QTest.mouseClick(self.wvTest, Qt.LeftButton, QPoint(x, y))

and

frame = self.wvTest.page().mainFrame()
print(frame.toHtml().encode('utf-8'))

I am aware of the docs as well as this page about porting to QWebEngineView but unable to convert C++ notation to a working Python code.

How can I adapt this to QWebEngineView in PyQt-5.7? Below is a fully working snippet for PyQt-5.5.1, which fails for the new PyQt-version:

  • for Button1: no mouse click reaction at all.
  • for Button2: AttributeError: 'QWebEnginePage' object has no attribute 'mainFrame', and when I delete the mainframe(): TypeError: toHtml(self, Callable[..., None]): not enough arguments.

import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication
from PyQt5.QtCore import QRect, Qt, QUrl, QPoint, QEvent
from PyQt5.QtTest import QTest
from PyQt5.Qt import PYQT_VERSION_STR

if PYQT_VERSION_STR=='5.5.1': from PyQt5 import QtWebKitWidgets else: from PyQt5 import QtWebEngineWidgets

class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.button1 = QPushButton('Button1', self) self.button1.clicked.connect(self.buttonOne) self.button1.setGeometry(QRect(10, 10, 90, 20)) self.button2 = QPushButton('Button2', self) self.button2.clicked.connect(self.buttonTwo) self.button2.setGeometry(QRect(110, 10, 90, 20)) if PYQT_VERSION_STR=='5.5.1': self.wvTest = QtWebKitWidgets.QWebView(self) else: self.wvTest = QtWebEngineWidgets.QWebEngineView(self) self.wvTest.setGeometry(QRect(10, 40, 430, 550)) self.wvTest.setUrl(QUrl('http://www.startpage.com')) self.wvTest.setObjectName('wvTest') self.setGeometry(300, 300, 450, 600) self.setWindowTitle('WebView minimalistic') self.show() def buttonOne(self): qp = QPoint(38, 314) QTest.mouseClick(self.wvTest, Qt.LeftButton, pos=qp) # or: QTest.mouseMove(self.wvTest, pos=self.qp) print('Button1 pressed.') def buttonTwo(self): frame = self.wvTest.page().mainFrame() print(frame.toHtml().encode('utf-8')) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())

Gombach answered 19/11, 2016 at 18:0 Comment(0)
N
8

The QWebEngineView class is not a drop-in replacement for QWebView. As the porting guide makes clear, many of the APIs have fundamentally changed, and some major features are completely missing. This will probably make it impossible to write a compatibility layer unless your browser implementation is very, very simple. It will most likely be easier to write a separate test-suite (unless you don't mind writing a huge amount of conditional code).

To start with, you will need to implement a work-around for QTBUG-43602. The current design of QWebEngineView means that an internal QOpenGLWidget handles mouse-events, so your code will need to get a new reference to that whenever the page is loaded:

class Example(QWidget):
    ...

    def initUI(self):
        ...    
        self._glwidget = None

        if PYQT_VERSION_STR=='5.5.1':
            self.wvTest = QtWebKitWidgets.QWebView(self)
        else:
            self.wvTest = QtWebEngineWidgets.QWebEngineView(self)
            self.wvTest.installEventFilter(self)
        ...

    def eventFilter(self, source, event):
        if (event.type() == QEvent.ChildAdded and
            source is self.wvTest and
            event.child().isWidgetType()):
            self._glwidget = event.child()
            self._glwidget.installEventFilter(self)
        elif (event.type() == QEvent.MouseButtonPress and
              source is self._glwidget):
            print('web-view mouse-press:', event.pos())
        return super().eventFilter(source, event)

    def buttonOne(self):
        qp = QPoint(38, 314)
        widget = self._glwidget or self.wvTest
        QTest.mouseClick(widget, Qt.LeftButton, pos=qp)

For accessing the html of the page, you will need some conditional code, because the web-engine API works asynchronously, and requires a callback. Also there are no built-in APIs for handling frames in web-engine (you need to use javascript for that), so everything needs to go through the web-page:

    def buttonTwo(self):
        if PYQT_VERSION_STR=='5.5.1':
            print(self.wvTest.page().toHtml())
        else:
            self.wvTest.page().toHtml(self.processHtml)

    def processHtml(self, html):
        print(html)
    
Nils answered 19/11, 2016 at 19:35 Comment(5)
Thanks a lot! This works flawlessly. However, by implementing it back to my original script, two minor follow-up questions came up: (1) Is there a possibibility to make clicking work for a different zoom factor of the QWebEngineView, i.e. self.wvTest.setZoomFactor(0.8)? (2) How can I save the html-code to a string for further processing rather than printing it? Sorry, I'm not familiar with callback-stuff at all.Gombach
(1) This seems like a completely separate question which has nothing to do with porting. Obviously if you change the zoom factor, the position of the input field will change, so you need to adjust it accordingly. (2) I have updated the example code in my answer.Nils
Somehow, (1) didn't work for a different ZoomFactor, although I adjusted the coordinates accordingly. I was thinking about a possible bug, but I have to look into it again. (2): So, there is no way to directly extract it... Fine, that will do. :) Thanks a lot for your help.Gombach
@nostradamus. (1) I tried it with QPoint(30, 270) and it works fine. (2) Yes, it's asynchronous, like several other APIs (e.g. runJavaScript).Nils
If you right click on the loaded webpage , you get another new _glwidget , you need to remove the source is self._glwidget check in the elif section to make it still workChastise

© 2022 - 2024 — McMap. All rights reserved.