用QWebEngineView在新窗口中打开链接 - 将链接重定向到一个单独的浮动浏览器窗口中

598 阅读7分钟

在PyQt5应用程序中使用QWebEngineView 作为文档(或文件)浏览器是一个相当普遍的做法,因为它允许使用熟悉的工具来创建文档。你可以建立HTML文档并将其与你的应用程序捆绑在一起(或远程托管),然后允许你的用户在应用程序中浏览它们。

然而,当文档包含指向外部资源的链接时,这就产生了一个问题--应该如何处理这些链接? 如果你的用户通过点击一系列的链接,最终在你的应用程序进入谷歌或Facebook,这将是一种奇怪的用户体验。避免这种情况的方法之一是强制在外部窗口或浏览器中打开某些链接,确保你的文档浏览器仍然只是这样。

在这个快速教程中,我们将看看如何在你的Qt浏览器中实现自定义链接处理,并利用它将点击的链接重定向到不同的窗口或用户的桌面浏览器。

网页浏览器的骨架代码如下所示。我们将对其进行修改以添加在新窗口中打开的行为。

  • PyQt5
  • PySide2
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView

import os
import sys


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setUrl(QUrl("https://www.mfitzp.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()
from PySide2.QtCore import QUrl
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtWebEngineWidgets import QWebEngineView

import os
import sys


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setUrl(QUrl("https://www.mfitzp.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

这将创建我们的基本浏览器窗口并导航到LearnPyQt主页。所有的链接将在同一个浏览器窗口中打开(正常的浏览器行为)。

The basic browser 基本的浏览器窗口。

为每个链接弹出一个新窗口

为了覆盖默认的导航行为,我们需要创建一个自定义的QWebEnginePage 类。这是一个Qt类,用于处理网页文档的浏览(和编辑),比如在QWebEngineView

通过创建一个自定义的QWebEnginePage 类,我们能够拦截.acceptNavigationRequest 信号并实现自定义行为。例如,我们可以拒绝链接以阻止导航,改变链接以导航到其他地方,或者(就像我们在这里做的那样)打开自定义查看器。在我们的具体案例中,我们将通过返回False拒绝默认行为,并通过打开一个新窗口,实现我们自己的行为。

对于我们的新窗口,我们创建一个自定义的网络引擎视图(与我们在主窗口中的类型相同),设置URL,然后显示该窗口。注意,我们需要保留对所创建窗口的引用(在external_windows 列表中),这样它就不会在退出这个方法时被销毁。

对于其他的事情,我们用super().acceptNavigationRequest() 传递给父类上的处理程序。

蟒蛇

from PyQt5.QtWebEngineWidgets import QWebEnginePage

class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            w = QWebEngineView()
            w.setUrl(url)
            w.show()

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

为了使用我们的自定义页面类,我们需要在浏览器上用.setPage() 。在这之后,任何导航都会通过自定义页面实例和我们的acceptNavigationRequest 处理程序发送。

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("http://google.com"))

完整的工作实例如下所示:

  • PyQt5
  • PySide2
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

import os
import sys


class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            w = QWebEngineView()
            w.setUrl(url)
            w.show()

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


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.mfitzp.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()
from PySide2.QtCore import QUrl
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

import os
import sys


class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            w = QWebEngineView()
            w.setUrl(url)
            w.show()

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


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.mfitzp.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

如果你运行这个例子并点击页面中的链接,每点击一个链接就会打开一个新窗口。你可以继续在这些外部窗口中正常浏览--它们使用标准的QWebEnginePage 类,所以没有我们的在新窗口中打开的行为。

Links are opened in a separate window 链接是在新窗口中打开的。

有条件地弹出一个新窗口

有时你只希望 "外部 "链接被弹出到一个单独的窗口中--在你的文档中的导航应该留在文档浏览器窗口中,只有外部链接(不是来自你的文档)才会被弹出到一个单独的窗口中。

由于我们可以访问被导航的URL,这就非常简单了。我们可以将该URL与一些模式进行比较(这里我们通过url.host() 使用主机名),然后选择弹出一个新的窗口,或者将其传递给默认的处理程序。

蟒蛇

class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if (_type == QWebEnginePage.NavigationTypeLinkClicked and
            url.host() != 'www.mfitzp.com'):
            # Pop up external links into a new window.
            w = QWebEngineView()
            w.setUrl(url)
            w.show()

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

现在在LearnPyQt网站上的导航是在主浏览器中进行的,但外部链接(比如到论坛)会被弹出到一个单独的窗口中。

External window popped up for external links 外部窗口的弹出仅用于外部链接。

重复使用一个外部窗口

在第一个例子中,我们为每个链接创建一个新的窗口,而不是在不存在的情况下创建一个新的窗口,然后将所有后续的链接点击发送到同一个窗口。为了得到这第二个行为,我们只需要持有一个对外部窗口的引用,并在创建一个新窗口之前检查它是否存在。

class WebEnginePage(QWebEnginePage):
    # Store second window.
    external_window = None

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        print(url, _type, isMainFrame)
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            if not self.external_window:
                self.external_window = QWebEngineView()

            self.external_window.setUrl(url)
            self.external_window.show()
            return False

        return super().acceptNavigationRequest(url,  _type, isMainFrame)

把这一点放到我们的例子中,就可以得到下面这个完整的例子:

  • PyQt5
  • PySide2
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

import sys


class CustomWebEnginePage(QWebEnginePage):
    # Store second window.
    external_window = None

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        print(url, _type, isMainFrame)
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            if not self.external_window:
                self.external_window = QWebEngineView()

            self.external_window.setUrl(url)
            self.external_window.show()
            return False

        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.mfitzp.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()
from PySide2.QtCore import QUrl
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

import sys


class CustomWebEnginePage(QWebEnginePage):
    # Store second window.
    external_window = None

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        print(url, _type, isMainFrame)
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            if not self.external_window:
                self.external_window = QWebEngineView()

            self.external_window.setUrl(url)
            self.external_window.show()
            return False

        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.mfitzp.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

当点击浏览器窗口中的一个链接时,一个新的窗口被创建。你可以在该窗口中正常浏览,但如果你在父窗口中点击一个新的链接,当前页面将被该链接取代。

在用户的默认浏览器中打开链接

你可能还想考虑在用户的默认浏览器中弹出外部链接。 这允许他们将链接加入书签,或像平常一样浏览,而不是在你的应用程序提供的受限的浏览器视图中。

为此,我们不需要创建一个窗口,我们可以直接将url发送到系统的默认处理程序。这是通过将url传递给QDesktopServices.openUrl() 方法来完成的。完整的工作实例如下所示:

  • PyQt5
  • PySide2
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from PyQt5.QtGui import QDesktopServices

import os
import sys


class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if (_type == QWebEnginePage.NavigationTypeLinkClicked and
            url.host() != 'www.mfitzp.com'):
            # Send the URL to the system default URL handler.
            QDesktopServices.openUrl(url)
            return False
        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.mfitzp.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()
from PySide2.QtCore import QUrl
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from PySide2.QtGui import QDesktopServices

import os
import sys


class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if (_type == QWebEnginePage.NavigationTypeLinkClicked and
            url.host() != 'www.mfitzp.com'):
            # Send the URL to the system default URL handler.
            QDesktopServices.openUrl(url)
            return False
        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.mfitzp.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

如果你运行这个例子并点击任何外部链接,你会看到它们在你系统的默认浏览器窗口中打开(这里显示为Chrome)。

Link opened in default browser window