Как реализовать адаптивный вид галереи с PySide2 - PullRequest
2 голосов
/ 09 октября 2019

Я работаю над приложением по управлению активами для нашей компании. Это автономное приложение Python3, использующее PySide2 и поддерживающее нашу базу данных. Предполагается, что одним из представлений, которые я пишу, будет реагирующая галерея в стиле HTML5: ресурсы отображаются в виде миниатюр, при наведении курсора мыши они отображают дополнительную информацию, а при щелчке они инициируют действие (например, открытие ресурса в соответствующем приложении). ).

Какой лучший способ реализовать это в PySide2 / PyQt5?

Поскольку я чувствую себя комфортно при реализации и стилизации чего-то подобного в HTML5, я склонен делать это с помощью QWebEngineView и динамически генерировать HTML и CSS в python, а затем использовать QWebEngineView.setHtml () для его отображения.

Это хороший способ сделать это внутри приложения PySide2, которое не используетHTML иначе? Существуют ли другие способы Qt-ish для создания динамичной, отзывчивой, стильной галереи?

Если бы я использовал QWebEngineView, как бы я мог перехватить нажатие пользователем на один из элементов HTML? Я нашел этот вопрос, который звучит так, как будто он может решить эту проблему: Захватить ответ сервера с помощью QWebEngineView . Есть ли более простое решение?

Ответы [ 2 ]

1 голос
/ 09 октября 2019

Qt предлагает множество альтернатив для того, что вы хотите (они не являются полными решениями, поскольку вы не четко указываете, что вам нужно):


Реализация эффекта наведения мыши не будет реализована, потому что я не эксперт в области интерфейса, но я сосредоточусь на общениимежду сторонами.


Чтобы передать информацию Python в JS, вы можете сделать это с помощью метода runJavaScript () QWebEnginePage и / или с QWebChannel, а обратная часть - с QWebChannel (я не правлюМысль о том, что QWebEngineUrlRequestInterceptor может быть альтернативным решением, но в этом случае предыдущие решения они проще). Так что в этом случае я буду использовать QWebChannel.

Идея состоит в том, чтобы зарегистрировать QObject, который отправляет информацию через сигналы (в данном случае JSON), на стороне 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}" alt="">
        </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;
}
0 голосов
/ 09 октября 2019

Любить ответ от @eyllanesc, используя QWebChannel ! Я не сталкивался с этим и не смел мечтать о связи в стиле AJAX между приложением PySide и WebView! Люблю это!

Вот менее гибкая / сложная альтернатива, которую я предложил тем временем, используя 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: //' и действовать соответственно.

Еще одно замечание: в нашем случае все файлы / images / ect живут в локальной файловой системе. ,Чтобы избежать исключения безопасности между источниками, мне пришлось предоставить baseUrl в setHtml () и задать для него тот же путь к файлу, на котором хранятся файлы.

html = createHtml()
baseUrl = "file://server01/"
page.setHtml(html, baseUrl=baseUrl)
...