PyQt5: mouseClick и исходный код в QWebEngineView

У меня есть рабочий сценарий, использующий PyQt-5.5.1, и теперь я хочу перенести его на новую версию PyQt (5.7). Адаптация большинства вещей прошла нормально, но я столкнулся с двумя серьезными проблемами: (1) выполнить (смоделированный) щелчок мышью, (2) получить доступ (скажем: печать) к исходному коду html веб-страницы, которая в настоящее время отображается в QWebView или QWebEngineView соответственно.

Например, я мог бы сделать следующее, используя QWebView в PyQt-5.5.1:

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

а также

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

Мне известны документы, а также эта страница о переносе на QWebEngineView, но неспособности преобразовать нотацию C ++ в рабочий код Python.

Как я могу адаптировать это к QWebEngineView в PyQt-5.7? Ниже приведен полностью рабочий фрагмент для PyQt-5.5.1, который не работает для новой версии PyQt:

  • для Button1: никакой реакции на щелчок мышью.
  • для Button2: AttributeError: 'QWebEnginePage' object has no attribute '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

если PYQT_VERSION_STR == '5.5.1': из PyQt5 импортировать QtWebKitWidgets иначе: из PyQt5 импортировать 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_())


person nostradamus    schedule 19.11.2016    source источник


Ответы (1)


Класс QWebEngineView не заменяет QWebView. Как поясняется в руководстве по портированию, многие API-интерфейсы коренным образом изменились, а некоторые основные функции полностью отсутствуют. Это, вероятно, сделает невозможным создание уровня совместимости, если ваша реализация в браузере не очень и очень проста. Скорее всего, будет проще написать отдельный набор тестов (если вы не против написать огромное количество условного кода).

Для начала вам нужно будет реализовать обходной путь для QTBUG-43602. Текущий дизайн QWebEngineView означает, что внутренний QOpenGLWidget обрабатывает события мыши, поэтому вашему коду потребуется новая ссылка на него всякий раз, когда страница загружается:

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)

Для доступа к HTML-странице вам понадобится некоторый условный код, потому что API веб-движка работает асинхронно и требует обратного вызова. Также нет встроенных API для обработки фреймов в веб-движке (для этого нужно использовать javascript), поэтому все должно проходить через веб-страницу:

    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)
person ekhumoro    schedule 19.11.2016
comment
Большое спасибо! Это работает безупречно. Однако, вернув его к моему исходному сценарию, возникли два незначительных дополнительных вопроса: (1) Есть ли возможность заставить работать щелчок для другого коэффициента масштабирования QWebEngineView, то есть self.wvTest.setZoomFactor(0.8)? (2) Как я могу сохранить html-код в строке для дальнейшей обработки, а не распечатывать ее? Извините, я вообще не знаком с функциями обратного вызова. - person nostradamus; 20.11.2016
comment
(1) Это совершенно отдельный вопрос, не имеющий ничего общего с портированием. Очевидно, что если вы измените коэффициент масштабирования, положение поля ввода изменится, поэтому вам необходимо настроить его соответствующим образом. (2) Я обновил пример кода в своем ответе. - person ekhumoro; 20.11.2016
comment
Почему-то (1) не сработало для другого ZoomFactor, хотя я соответствующим образом скорректировал координаты. Я думал о возможной ошибке, но мне нужно разобраться в ней еще раз. (2): Итак, нет возможности напрямую извлечь его ... Хорошо, подойдет. :) Спасибо большое за вашу помощь. - person nostradamus; 20.11.2016
comment
@nostradamus. (1) Я пробовал это с QPoint(30, 270), и он отлично работает. (2) Да, это асинхронно, как и несколько других API (например, runJavaScript). - person ekhumoro; 20.11.2016
comment
Спасибо ! но если щелкнуть правой кнопкой мыши загруженную веб-страницу, вы получите еще один новый _glwidget, это все еще верно? - person iMath; 19.03.2021