如何在PyQt5中使用QtWebEngine createWindow

How to use QtWebEngine createWindow in PyQt5

我正在尝试制作一个包含 QWebEngineView 的 window。现在我希望浏览器能够处理创建 window 或 _blank 类型的触发器,或者在需要时专门在新的 window 中打开 URL。在下面的代码中,当浏览器需要创建一个window时,会调用createwindow(),但不会打开window。请帮助我在以下情况下需要时通过浏览器打开新的 window 的正确方法。

import json
import sys
import os
import time
import json
import sys
import platform

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from PyQt5.QtWebEngineWidgets import QWebEngineSettings as QWebSettings
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import QUrl


from main_dash import Ui_MainWindow


class MainDashWindow(QMainWindow):
    socketSignal = QtCore.pyqtSignal(object)  # must be defined in class level

    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        # self.ui.setupUi(self)
        # ui = Ui_MainWindow()
        self.isMax = 0

        self.ui.setupUi(self)

    def re_setup(self):
        self.page = WebEnginePage2()
        self.page.Notifications = True
        self.ui.full_content_container.hide()
        self.page.createWindow = True

        self.page.settings().setAttribute(QWebSettings.JavascriptEnabled, True)
        self.page.settings().setAttribute(QWebSettings.JavascriptCanOpenWindows, True)
        self.page.settings().setAttribute(
            QWebSettings.JavascriptCanAccessClipboard, True)

        # self.full_content_container is the webengineview in the mainUi file
        self.ui.full_content_container.setPage(self.page)
        # self.ui.full_content_container.setContextMenuPolicy(Qt.NoContextMenu)

        url6 = "...../icons_nec/ui/index.html"

        self.ui.full_content_container.setUrl(QtCore.QUrl(url6))
        self.ui.full_content_container.loadFinished.connect(
            self.on_load_finished)

        ########################################################################
        self.show()
        ## ==> END ##

    def get_path(self, filename):
        if hasattr(sys, "_MEIPASS"):
            return f'{os.path.join(sys._MEIPASS, filename)}'
        else:
            return f'{filename}'

    def on_load_finished(self):
        self.ui.full_content_container.show()


class WebEnginePage2(QWebEnginePage):
    def __init__(self, *args, **kwargs):
        QWebEnginePage.__init__(self, *args, **kwargs)
        self.featurePermissionRequested.connect(
            self.onFeaturePermissionRequested)

    def onFeaturePermissionRequested(self, url, feature):
        self.setFeaturePermission(
            url, feature, QWebEnginePage.PermissionGrantedByUser)

    def createWindow(self,
                    wintype: QWebEnginePage.WebWindowType) -> QWebEngineView:
        """Called by Qt when a page wants to create a new tab or window.

        In case the user wants to open a resource in a new tab, we use the
        createWindow handling of the main page to achieve that.

        See WebEngineView.createWindow for details.
        """
        return self.page().inspectedPage().view().createWindow(wintype)
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        print("in navigation")
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            w = QWebEngineView()
            w.setUrl(url)
            w.show()
            print("link detected")

            # Keep reference to external window, so it isn't cleared up.
            self.external_windows.append(w)
            return False
            # QDesktopServices.openUrl(url)
        # elif _type == QWebEnginePage.NavigationType
        return super().acceptNavigationRequest(url,  _type, isMainFrame)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainDashWindow()
    window.re_setup()

    sys.exit(app.exec_())

这里是上面导入的Ui_MainWindow文件。 我需要弄清楚在这种情况下,如果网站需要,我该如何实现 createWindow()。

from PyQt5 import QtWebEngineWidgets
import all_icons_rc
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1280, 720)
        MainWindow.setMinimumSize(QtCore.QSize(1280, 720))
        MainWindow.setMaximumSize(QtCore.QSize(1920, 1080))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_2.setSpacing(0)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.Header = QtWidgets.QFrame(self.centralwidget)
        self.Header.setMinimumSize(QtCore.QSize(0, 40))
        self.Header.setMaximumSize(QtCore.QSize(16777215, 50))
        self.Header.setStyleSheet("background-color: rgb(33, 37, 41);")
        self.Header.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.Header.setFrameShadow(QtWidgets.QFrame.Raised)
        self.Header.setObjectName("Header")
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.Header)
        self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_4.setSpacing(0)
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.frame_2 = QtWidgets.QFrame(self.Header)
        self.frame_2.setStyleSheet("")
        self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame_2.setObjectName("frame_2")
        self.horizontalLayout_4.addWidget(self.frame_2)
        self.frame = QtWidgets.QFrame(self.Header)
        self.frame.setMaximumSize(QtCore.QSize(150, 16777215))
        self.frame.setStyleSheet("/*background-color: rgb(85, 255, 0);*/")
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame.setObjectName("frame")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame)
        self.horizontalLayout.setSpacing(15)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.minimize_btn = QtWidgets.QPushButton(self.frame)
        self.minimize_btn.setText("")
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(
            ":/icons/Icons/icons8_macos_minimize_50px.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.minimize_btn.setIcon(icon)
        self.minimize_btn.setIconSize(QtCore.QSize(30, 30))
        self.minimize_btn.setFlat(True)
        self.minimize_btn.setObjectName("minimize_btn")
        self.horizontalLayout.addWidget(self.minimize_btn)
        self.maximize_btn = QtWidgets.QPushButton(self.frame)
        self.maximize_btn.setText("")
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap(
            ":/icons/Icons/icons8_maximize_window_50px.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.maximize_btn.setIcon(icon1)
        self.maximize_btn.setIconSize(QtCore.QSize(30, 30))
        self.maximize_btn.setFlat(True)
        self.maximize_btn.setObjectName("maximize_btn")
        self.horizontalLayout.addWidget(self.maximize_btn)
        self.close_btn = QtWidgets.QPushButton(self.frame)
        self.close_btn.setText("")
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap(
            ":/icons/Icons/icons8_Close_50px_2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.close_btn.setIcon(icon2)
        self.close_btn.setIconSize(QtCore.QSize(30, 30))
        self.close_btn.setFlat(True)
        self.close_btn.setObjectName("close_btn")
        self.horizontalLayout.addWidget(self.close_btn)
        self.horizontalLayout_4.addWidget(self.frame)
        self.verticalLayout_2.addWidget(self.Header)
        self.body = QtWidgets.QFrame(self.centralwidget)
        self.body.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.body.setFrameShadow(QtWidgets.QFrame.Raised)
        self.body.setLineWidth(0)
        self.body.setObjectName("body")
        self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.body)
        self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_3.setSpacing(0)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.full_content_container = QtWebEngineWidgets.QWebEngineView(
            self.body)#<----This is webengineview
        self.full_content_container.setStyleSheet("background-color: rgb(85, 255, 255);\n"
                                                "border:none;")
       
        self.full_content_container.setObjectName("full_content_container")
        self.verticalLayout_3.addWidget(self.full_content_container)
        self.verticalLayout_2.addWidget(self.body)
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))

现在,请忽略图标。

index.html 正在加载到 webengineview 中进行测试的文件片段

<!DOCTYPE html>
<html>
<body>

<h1>The a target attribute</h1>

<p>Open link in a new window or tab: <a href="https://pathor.in" target="_blank">Visit PathOr!</a></p>

</body>
</html>

更新:

下面是基于您的示例代码的实现。如果您将 WebEnginePage2 class 完全替换为这个:

,一切都应该按预期工作
class WebEnginePage2(QWebEnginePage):
    _windows = {}

    @classmethod
    def newWindow(cls):
        window = QWebEngineView()
        window.setObjectName(f'window-{id(window)}')
        window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
        window.destroyed.connect(
            lambda window: cls._windows.pop(window.objectName(), None))
        window.setPage(cls(window))
        cls._windows[window.objectName()] = window
        return window

    def __init__(self, *args, **kwargs):
        QWebEnginePage.__init__(self, *args, **kwargs)
        self.featurePermissionRequested.connect(
            self.onFeaturePermissionRequested)
        self.geometryChangeRequested.connect(self.handleGeometryChange)

    def handleGeometryChange(self, rect):
        view = self.view()
        window = QtGui.QWindow.fromWinId(view.winId())
        if window is not None:
            rect = rect.marginsRemoved(window.frameMargins())
        view.resize(rect.size())
        view.show()

    def createWindow(self, mode):
        window = self.newWindow()
        if mode != QtWebEngineWidgets.QWebEnginePage.WebDialog:
            window.resize(800, 600)
            window.show()
        return window.page()

    def onFeaturePermissionRequested(self, url, feature):
        self.setFeaturePermission(
            url, feature, QWebEnginePage.PermissionGrantedByUser)

您需要创建浏览器的新实例 window 并在 window 列表中保留对它的引用。确保 window 的大小合适也很重要,否则它将不可见。对于用javascript打开的windows,可以使用geometryChangeRequested signal来设置请求的大小,否则应该使用默认值。

下面是一个实现基本功能的简单演示。希望如何使它适应您自己的应用程序应该是显而易见的:

from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets

class Browser(QtWebEngineWidgets.QWebEngineView):
    _windows = set()

    @classmethod
    def _removeWindow(cls, window):
        cls._windows.discard(window)

    @classmethod
    def newWindow(cls):
        window = cls()
        cls._windows.add(window)
        return window

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
        self.page().geometryChangeRequested.connect(self.handleGeometryChange)
        self.page().titleChanged.connect(self.setWindowTitle)

    def closeEvent(self, event):
        self._removeWindow(self)
        event.accept()

    def handleGeometryChange(self, rect):
        window = QtGui.QWindow.fromWinId(self.winId())
        if window is not None:
            rect = rect.marginsRemoved(window.frameMargins())
        self.resize(rect.size())
        self.setFocus()
        self.show()

    def createWindow(self, mode):
        window = self.newWindow()
        if mode != QtWebEngineWidgets.QWebEnginePage.WebDialog:
            window.resize(800, 600)
            window.show()
        return window

html = """
<html><head><title>Test Page</title>
<script type="text/javascript"><!--
var count = 0
var url = 'https://www.google.com'
function newWindow() {
    count += 1
    window.open(url, 'Test' + count, 'width=640,height=480');
}
--></script>
</head>
<body>
<input type="button" value="New Window" onclick="newWindow()" />
<p><a href="https://www.google.com" target="_blank">Blank</a></p>
</body>
</html>"""

if __name__ == '__main__':

    import sys
    app = QtWidgets.QApplication(sys.argv)
    browser = Browser()
    browser.setHtml(html)
    browser.setGeometry(600, 100, 400, 200)
    browser.show()
    sys.exit(app.exec_())