如何使用 PySide2 实现响应式画廊视图
Posted
技术标签:
【中文标题】如何使用 PySide2 实现响应式画廊视图【英文标题】:How to implement a responsive gallery view with PySide2 【发布时间】:2019-10-09 01:45:30 【问题描述】:我正在为我们公司开发一个资产管理应用程序。这是一个使用 PySide2 并与我们的数据库后端对话的独立 Python3 应用程序。我正在编写的视图之一应该是 html5 样式的响应式图库:资产显示为缩略图,鼠标悬停时显示额外信息,单击时启动操作(例如在适当的应用程序中打开资产)。
在 PySide2/PyQt5 中实现此功能的最佳方式是什么?
由于我很乐意在 HTML5 中实现和设置类似的样式,我倾向于使用 QWebEngineView 并在 python 中动态生成 HTML 和 CSS,然后使用 QWebEngineView.setHtml()显示它。
这是在不使用 HTML 的 PySide2 应用程序中执行此操作的好方法吗?是否有更多类似 Qt 的方法来实现动态、响应式、可样式化的图库?
如果我使用 QWebEngineView,我将如何拦截用户点击其中一个 HTML 元素?我发现了这个问题,听起来这可能是一个解决方案:Capture server response with QWebEngineView。有没有更简单的解决方案?
【问题讨论】:
【参考方案1】:Qt 提供了许多您想要的替代方案(它们不是完整的解决方案,因为您没有明确指出您需要什么):
Qt 小部件:PySide2 composite widget Hover Effect,
Qt QML:PySide2/QML populate and animate Gridview model/delegate,
或者QWebEngineView,重点是我当前的答案。
鼠标悬停效果的实现将不会实现,因为我不是前端专家,但我会专注于各方之间的沟通。
要将 Python 信息传达给 JS,您可以使用 QWebEnginePage 的 runjavascript() 方法和/或使用 QWebChannel,以及使用 QWebChannel 的相反部分(我不排除 QWebEngineUrlRequestInterceptor 可能是替代解决方案的想法但在这种情况下,以前的解决方案更简单)。所以在这种情况下,我将使用 QWebChannel。
这个想法是注册一个通过信号(在本例中为 JSON)发送信息的 QObject,在 javascript 解析 JSON 并创建动态 HTML 的旁边,然后在任何事件(如点击)之前调用 QObject 的插槽.
综合以上,解决办法是:
├── index.html
├── index.js
└── main.py
import json
from PySide2 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, QtWebChannel
class GalleryManager(QtCore.QObject):
dataChanged = QtCore.Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._data = []
self._is_loaded = False
@QtCore.Slot(str)
def make_action(self, identifier):
print(identifier)
@QtCore.Slot()
def initialize(self):
self._is_loaded = True
self.send_data()
def send_data(self):
if self._is_loaded:
self.dataChanged.emit(json.dumps(self._data))
@property
def data(self):
return self._data
@data.setter
def data(self, d):
self._data = d
self.send_data()
if __name__ == "__main__":
import os
import sys
# sys.argv.append("--remote-debugging-port=8000")
app = QtWidgets.QApplication(sys.argv)
current_dir = os.path.dirname(os.path.realpath(__file__))
view = QtWebEngineWidgets.QWebEngineView()
channel = QtWebChannel.QWebChannel(view)
gallery_manager = GalleryManager(view)
channel.registerObject("gallery_manager", gallery_manager)
view.page().setWebChannel(channel)
def on_load_finished(ok):
if not ok:
return
data = []
for i, path in enumerate(
(
"https://source.unsplash.com/pWkk7iiCoDM/400x300",
"https://source.unsplash.com/aob0ukAYfuI/400x300",
"https://source.unsplash.com/EUfxH-pze7s/400x300",
"https://source.unsplash.com/M185_qYH8vg/400x300",
"https://source.unsplash.com/sesveuG_rNo/400x300",
"https://source.unsplash.com/AvhMzHwiE_0/400x300",
"https://source.unsplash.com/2gYsZUmockw/400x300",
"https://source.unsplash.com/EMSDtjVHdQ8/400x300",
"https://source.unsplash.com/8mUEy0ABdNE/400x300",
"https://source.unsplash.com/G9Rfc1qccH4/400x300",
"https://source.unsplash.com/aJeH0KcFkuc/400x300",
"https://source.unsplash.com/p2TQ-3Bh3Oo/400x300",
)
):
d = "url": path, "identifier": "id-".format(i)
data.append(d)
gallery_manager.data = data
view.loadFinished.connect(on_load_finished)
filename = os.path.join(current_dir, "index.html")
view.load(QtCore.QUrl.fromLocalFile(filename))
view.resize(640, 480)
view.show()
sys.exit(app.exec_())
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="index.js"> </script>
</head>
<body>
<div class="container">
<h1 class="font-weight-light text-center text-lg-left mt-4 mb-0">Thumbnail Gallery</h1>
<hr class="mt-2 mb-5">
<div id="container" class="row text-center text-lg-left">
</div>
</div>
</body>
</html>
var gallery_manager = null;
new QWebChannel(qt.webChannelTransport, function (channel)
gallery_manager = channel.objects.gallery_manager;
gallery_manager.dataChanged.connect(populate_gallery);
gallery_manager.initialize();
);
function populate_gallery(data)
const container = document.getElementById('container');
// clear
while (container.firstChild)
container.removeChild(container.firstChild);
// parse json
var d = JSON.parse(data);
// fill data
for (const e of d)
var identifier = e["identifier"];
var url = e["url"];
var div_element = create_div(identifier, url)
container.appendChild(div_element);
function create_div(identifier, url)
var html = `
<div class="d-block mb-4 h-100">
<img class="img-fluid img-thumbnail" src="$url" >
</div>
`
var div_element = document.createElement("div");
div_element.className = "col-lg-3 col-md-4 col-6"
div_element.innerHTML = html;
div_element.addEventListener('click', function (event)
gallery_manager.make_action(identifier);
);
return div_element;
【讨论】:
感谢@eyllanesc 的详细解答!到目前为止,我总是使用 PySide/PyQt 应用程序或 WebApps。能够在我的 PySide 应用程序中使用 HTML5 元素的想法令人兴奋!感谢您解释QWebChannel,我以前没有遇到过!【参考方案2】:喜欢@eyllanesc 使用 QWebChannel 提供的答案!我没有遇到过这种情况,也不敢梦想在 PySide 应用程序和 WebView 之间进行 AJAX 风格的通信!爱它!
这是我同时使用QWebEnginePage.acceptNavigationRequest() 提出的一个不太灵活/精细的替代方案。我更喜欢 QWebChannel 答案,但其他人可能会发现此选项也很有帮助。
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
class MyPage(QWebEnginePage):
def acceptNavigationRequest(self, url, type, isMainFrame):
print(url, type, isMainFrame)
if url.toString().startswith('app://'):
print('intercepted click, do stuff')
return False
return True
def createHtml():
html = """
<html>
<head>
<style>
.item
position: relative;
.animgif
display: none;
position: absolute;
top: 0;
left: 0;
.item:hover .animgif
display: block;
</style>
</head>
<body>
<a href="app://action/click?id=1234">
<div class="item">
<img class="thumb" src="file://server01/path/to/thumbnail.png">
<img class="animgif" src="file://server/path/to/thumbnail.gif">
</div>
</a>
</body>
</html>
"""
return html
if __name__ == '__main__':
import sys
from PySide2 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
page = MyPage()
view = QWebEngineView()
view.setPage(page)
html = createHtml()
baseUrl = "file://server01/"
page.setHtml(html, baseUrl=baseUrl)
view.show()
sys.exit(app.exec_())
这个想法是动态创建 html 并使用 page.setHtml(html) 将其加载到视图上。在此示例中,createHtml() 函数是初级的,但显示了意图。子类化 QWebEnginePage 允许您覆盖 acceptNavigationRequest(),这允许您拦截点击并决定如何处理它们。在此示例中,我选择使用和检测协议“app://”并采取相应措施。
还有一点需要注意的是,在我们的例子中,所有文件/图像/等都存在于本地文件系统中。为了避免跨域安全异常,我必须在 setHtml() 中提供 baseUrl,并将其设置为托管文件的相同文件路径。
html = createHtml()
baseUrl = "file://server01/"
page.setHtml(html, baseUrl=baseUrl)
【讨论】:
以上是关于如何使用 PySide2 实现响应式画廊视图的主要内容,如果未能解决你的问题,请参考以下文章