QtWebView 中的页内 href 无法显示任何内容
In-Page href in QtWebView fails to display anything
因此,尝试将其归结为简单的部分。
我有这样的 href 条目:
<a href="#section_8">
linktext
</a>
如果我通过 Qts setContent 加载页面,这些页面内链接会起作用,但是,由于我的内容经常超过 2MB,我必须使用 urlhandler 方法。所以,快速概览,看起来像这样:
scheme.SchemeHandler.register(lambda: webpage, "reportstext")
self.loader = QWebEngineView()
self.loader.setUrl(QUrl("conapp://reportstext"))
SchemeHandler 保留应用程序内部内容的字典,并将其全部保留在 conapp://<contentname>
之后
现在,这行得通了。我可以将 Web 内容提供给 WebView。也就是说,直到我尝试使用开头提到的页内链接。然后我得到的只是一个空白页面,没有调试消息,没有崩溃,什么都没有。只是空白。也没有请求发送到方案处理程序。
如何让这些工作?
如果我将内容导出到 .html 文件并在浏览器中打开它,它会按预期工作,所以 Qt 端有些东西没有按我预期的那样工作。
完整示例,需要 PyQt5:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize, QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QBuffer, QIODevice
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
QWebEngineUrlRequestJob)
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
import sys
app = QApplication(sys.argv)
class SchemeHandler(QWebEngineUrlSchemeHandler):
def __init__(self):
super(SchemeHandler, self).__init__()
QWebEngineProfile.defaultProfile().installUrlSchemeHandler(b'conapp', self)
self._handlers = {}
def requestStarted(self, job):
url = job.requestUrl()
print("Got request for {}".format(url.toDisplayString()))
request = url.toString().split("//")[1]
buf = QBuffer(parent=self)
buf.open(QIODevice.WriteOnly)
buf.write(self._handlers[request]().encode("utf-8"))
buf.seek(0)
buf.close()
job.reply("text/html".encode("ascii"), buf)
def register(self, contentgenerator, contentname):
self._handlers[contentname] = contentgenerator
return contentname
SchemeHandler = SchemeHandler()
class ReportMenu(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(800, 600))
self.late_init()
def late_init(self):
def webpage():
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>
ConParser Output
</title>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
</head>
<body>
<div class="navbar">
<a href="#section_0">
MVZ Arztfallzähler
</a>
</div>
</body>
</html>"""
SchemeHandler.register(webpage, "reportstext")
self.loader = QWebEngineView()
self.loader.setUrl(QUrl("conapp://reportstext"))
self.report_loader = QWebEngineView()
self.gridLayout = QGridLayout(self)
self.gridLayout.addWidget(self.loader, 0, 0)
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
centralWidget.setLayout(self.gridLayout)
self.showMaximized()
self.loader.loadFinished.connect(self.load_completed)
def load_completed(self, *args):
if not args[0]:
print("Load failed:", args)
menu = ReportMenu()
ret_code = app.exec_()
sys.exit(ret_code)
如果你现在把它拿出来换掉
self.loader.setUrl(QUrl("conapp://reportstext"))
为
self.loader.setHtml(webpage())
这会产生页内网址的预期效果,但是,这有 2 MB 的内容限制,我经常超过这个限制。
这似乎是由底层 Chrome 引擎处理 base-url 的方式问题引起的。您的示例中的书签锚点是 relative,但是如果您将其设置为 absolute,如下所示:
<a href="conapp://reportstext#section_0">
MVZ Arztfallzähler
</a>
<p style="margin-top: 1500px">
<a name="section_0">HELLO WORLD</a>
</p>
您的示例将按预期工作。鉴于此,我希望可以通过添加这样的 <base>
标签让相关书签自动工作:
<head>
<base href="conapp://reportstext">
</head>
但遗憾的是,Chrome 对 <base>
标记的实现似乎被破坏了。它可以 部分 通过在 href 中添加尾部斜杠来解决:
<base href="conapp://reportstext/">
然后您的自定义 scheme-handler 将在页面上收到对 所有 相对 url 的请求。但是一个不想要的 side-effect 是 Chrome 然后像这样解释相对书签锚点的 hrefs:conapp://reportstext/#section_0
。因此,您的 scheme-handler 不会导航到书签,而是收到一个无效的 url.
请求
AFAICS,似乎没有任何其他方法可以拦截书签锚点的导航请求。我尝试了 QWebEngineUrlRequestInterceptor class and reimplementing QWebEnginePage.acceptNavigationRequest(),但与自定义 scheme-handler 一样,它们只是不会被要求使用书签锚点。似乎所有处理都发生在 Chrome.
内
解决上述问题的一种有点老套的方法是在页面加载时 运行 一些 javascript,它只是重写所有书签锚点,使它们具有绝对 hrefs。
这是适用于您的示例的基本实现:
class ReportMenu(QMainWindow):
...
def load_completed(self, ok):
if ok:
page = self.loader.page()
page.runJavaScript("""
url = window.location.href
links = document.querySelectorAll('a')
for (index = 0; index < links.length; ++index) {
link = links[index]
href = link.getAttribute('href')
if (href && href[0] == '#') {
link.href = url + href
}
}
""")
因此,尝试将其归结为简单的部分。 我有这样的 href 条目:
<a href="#section_8">
linktext
</a>
如果我通过 Qts setContent 加载页面,这些页面内链接会起作用,但是,由于我的内容经常超过 2MB,我必须使用 urlhandler 方法。所以,快速概览,看起来像这样:
scheme.SchemeHandler.register(lambda: webpage, "reportstext")
self.loader = QWebEngineView()
self.loader.setUrl(QUrl("conapp://reportstext"))
SchemeHandler 保留应用程序内部内容的字典,并将其全部保留在 conapp://<contentname>
现在,这行得通了。我可以将 Web 内容提供给 WebView。也就是说,直到我尝试使用开头提到的页内链接。然后我得到的只是一个空白页面,没有调试消息,没有崩溃,什么都没有。只是空白。也没有请求发送到方案处理程序。
如何让这些工作?
如果我将内容导出到 .html 文件并在浏览器中打开它,它会按预期工作,所以 Qt 端有些东西没有按我预期的那样工作。
完整示例,需要 PyQt5:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize, QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QBuffer, QIODevice
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
QWebEngineUrlRequestJob)
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
import sys
app = QApplication(sys.argv)
class SchemeHandler(QWebEngineUrlSchemeHandler):
def __init__(self):
super(SchemeHandler, self).__init__()
QWebEngineProfile.defaultProfile().installUrlSchemeHandler(b'conapp', self)
self._handlers = {}
def requestStarted(self, job):
url = job.requestUrl()
print("Got request for {}".format(url.toDisplayString()))
request = url.toString().split("//")[1]
buf = QBuffer(parent=self)
buf.open(QIODevice.WriteOnly)
buf.write(self._handlers[request]().encode("utf-8"))
buf.seek(0)
buf.close()
job.reply("text/html".encode("ascii"), buf)
def register(self, contentgenerator, contentname):
self._handlers[contentname] = contentgenerator
return contentname
SchemeHandler = SchemeHandler()
class ReportMenu(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(800, 600))
self.late_init()
def late_init(self):
def webpage():
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>
ConParser Output
</title>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
</head>
<body>
<div class="navbar">
<a href="#section_0">
MVZ Arztfallzähler
</a>
</div>
</body>
</html>"""
SchemeHandler.register(webpage, "reportstext")
self.loader = QWebEngineView()
self.loader.setUrl(QUrl("conapp://reportstext"))
self.report_loader = QWebEngineView()
self.gridLayout = QGridLayout(self)
self.gridLayout.addWidget(self.loader, 0, 0)
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
centralWidget.setLayout(self.gridLayout)
self.showMaximized()
self.loader.loadFinished.connect(self.load_completed)
def load_completed(self, *args):
if not args[0]:
print("Load failed:", args)
menu = ReportMenu()
ret_code = app.exec_()
sys.exit(ret_code)
如果你现在把它拿出来换掉
self.loader.setUrl(QUrl("conapp://reportstext"))
为
self.loader.setHtml(webpage())
这会产生页内网址的预期效果,但是,这有 2 MB 的内容限制,我经常超过这个限制。
这似乎是由底层 Chrome 引擎处理 base-url 的方式问题引起的。您的示例中的书签锚点是 relative,但是如果您将其设置为 absolute,如下所示:
<a href="conapp://reportstext#section_0">
MVZ Arztfallzähler
</a>
<p style="margin-top: 1500px">
<a name="section_0">HELLO WORLD</a>
</p>
您的示例将按预期工作。鉴于此,我希望可以通过添加这样的 <base>
标签让相关书签自动工作:
<head>
<base href="conapp://reportstext">
</head>
但遗憾的是,Chrome 对 <base>
标记的实现似乎被破坏了。它可以 部分 通过在 href 中添加尾部斜杠来解决:
<base href="conapp://reportstext/">
然后您的自定义 scheme-handler 将在页面上收到对 所有 相对 url 的请求。但是一个不想要的 side-effect 是 Chrome 然后像这样解释相对书签锚点的 hrefs:conapp://reportstext/#section_0
。因此,您的 scheme-handler 不会导航到书签,而是收到一个无效的 url.
AFAICS,似乎没有任何其他方法可以拦截书签锚点的导航请求。我尝试了 QWebEngineUrlRequestInterceptor class and reimplementing QWebEnginePage.acceptNavigationRequest(),但与自定义 scheme-handler 一样,它们只是不会被要求使用书签锚点。似乎所有处理都发生在 Chrome.
内解决上述问题的一种有点老套的方法是在页面加载时 运行 一些 javascript,它只是重写所有书签锚点,使它们具有绝对 hrefs。
这是适用于您的示例的基本实现:
class ReportMenu(QMainWindow):
...
def load_completed(self, ok):
if ok:
page = self.loader.page()
page.runJavaScript("""
url = window.location.href
links = document.querySelectorAll('a')
for (index = 0; index < links.length; ++index) {
link = links[index]
href = link.getAttribute('href')
if (href && href[0] == '#') {
link.href = url + href
}
}
""")