Foto-Oberfläche

Eine QML-App für Touch-Geräte, die einen Repeater mit einem FolderListModel verwendet, um auf Inhalte in einem Ordner zuzugreifen, und einen PinchHandler, um Pinch-Gesten auf den abgerufenen Inhalten zu verarbeiten.

Photo Surface demonstriert, wie ein Repeater mit einem FolderListModel und einem FolderDialog verwendet wird, um auf Bilder aus einem vom Benutzer ausgewählten Ordner zuzugreifen, und wie das Ziehen, Drehen und Zoomen innerhalb desselben Elements mit Qt Quick Input Handlers gehandhabt werden kann.

Der gesamte Anwendungscode ist in einer QML-Datei enthalten, photosurface.qml. Inline-JavaScript-Code wird zum Platzieren, Drehen und Skalieren von Bildern auf der Fotooberfläche verwendet.

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorauszuführen, öffnen Sie den Modus Welcome und wählen Sie das Beispiel unter Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.

Erstellen des Hauptfensters

Um das Hauptfenster für die Photo Surface-App zu erstellen, verwenden wir den QML-Typ Window als Stammelement. Er richtet das Fenster automatisch für die Verwendung mit Qt Quick grafischen Typen:

Window {
    id: root
    visible: true
    width: 1024; height: 600
    color: "black"
    title: Application.displayName + " : " + folderModel.folder
    property real defaultSize: 200
    property real surfaceViewportRatio: 1.5
    property var imageNameFilters: ["*.png", "*.jpg", "*.gif"] // overridden in main.cpp

Zugriff auf den Inhalt von Ordnern

Wir verwenden einen Repeater QML-Typ zusammen mit FolderListModel, um zumindest die GIF-, JPG- und PNG-Bilder in einem Ordner anzuzeigen (obwohl main.cpp die Liste der unterstützten Bildtypen erweitern kann):

        Repeater {
            model: FolderListModel {
                id: folderModel
                objectName: "folderModel"
                showDirs: false
                nameFilters: root.imageNameFilters
            }

Um den Typ FolderListModel zu verwenden, müssen wir ihn importieren:

import Qt.labs.folderlistmodel

Wir verwenden FolderDialog, um den Benutzern die Möglichkeit zu geben, den Ordner auszuwählen, der die Bilder enthält:

    FolderDialog {
        id: folderDialog
        title: qsTr("Choose a folder with some images")
        onAccepted: folderModel.folder = selectedFolder
    }

Um den Typ FolderDialog zu verwenden, fügen wir die folgende Importanweisung hinzu:

import QtQuick.Dialogs

Wir verwenden die Funktion folderDialog.open(), um den Dateidialog zu öffnen, wenn die anfängliche Diashow beendet ist, es sei denn, es wurde ein Ordnerpfad als Befehlszeilenargument angegeben:

        Component.onDestruction: {
            folderIcon.visible = true
            const lastArg = Application.arguments.slice(-1)[0]
            const standardPicturesLocations = StandardPaths.standardLocations(StandardPaths.PicturesLocation)
            const hasValidPicturesLocation = standardPicturesLocations.length > 0
            if (hasValidPicturesLocation)
                folderDialog.currentFolder = standardPicturesLocations[0]
            if (/.*hotosurface.*|--+/.test(lastArg)) {
                if (hasValidPicturesLocation)
                    folderModel.folder = standardPicturesLocations[0]
                else
                    folderDialog.open()
            }

Die Benutzer können auch auf das Symbol des Ordnerdialogs klicken, um ihn zu öffnen. Wir verwenden einen Image QML-Typ, um das Symbol anzuzeigen. Innerhalb des Image-Typs verwenden wir ein TapHandler mit dem onTapped Signalhandler, um die Funktion folderDialog.open() aufzurufen:

    Image {
        id: folderIcon
        visible: false
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.margins: 10
        source: "resources/folder.png"

        TapHandler { onTapped: folderDialog.open() }

        HoverHandler { id: folderMouse }

        ToolTip.visible: folderMouse.hovered
        ToolTip.text: qsTr(`Open an image directory (${openShortcut.nativeText})`)
        ToolTip.delay: 1000

        Shortcut {
            id: openShortcut
            sequence: StandardKey.Open
            onActivated: folderDialog.open()
        }
    }

Anzeige von Bildern auf der Fotooberfläche

Wir verwenden Rectangle als Delegierten für Repeater, um einen Rahmen für jedes Bild bereitzustellen, das FolderListModel im ausgewählten Ordner findet. Wir verwenden die JavaScript-Methoden Math(), um die Rahmen zufällig auf der Fotooberfläche zu platzieren und sie in zufälligen Winkeln zu drehen, sowie die Bilder zu skalieren. Die Farbe des Rahmens zeigt den Zustand der Interaktion an:

            delegate: Rectangle {
                required property date fileModified
                required property string fileName
                required property url fileUrl
                id: photoFrame
                objectName: "frame-" + fileName
                width: image.width * (1 + 0.10 * image.height / image.width)
                height: image.height * 1.10
                scale: root.defaultSize / Math.max(image.sourceSize.width, image.sourceSize.height)
                border.color: pinchHandler.active || dragHandler.active ? "darkturquoise"
                                                                        : mouse.hovered ? "darkseagreen"
                                                                                        : "saddlebrown"
                border.width: 3 / scale
                antialiasing: true
                Component.onCompleted: {
                    x = Math.random() * root.width - width / 2
                    y = Math.random() * root.height - height / 2
                    rotation = Math.random() * 13 - 6
                }

                Image {
                    id: image
                    anchors.centerIn: parent
                    fillMode: Image.PreserveAspectFit
                    source: photoFrame.fileUrl
                    antialiasing: true
                }

Handhabung von Zieh- und Kneifgesten und der Maus

Wir verwenden ein DragHandler und ein PinchHandler in jedem Fotorahmen, um das Ziehen, Zoomen und Drehen zu steuern:

                PinchHandler {
                    id: pinchHandler
                    minimumRotation: -360
                    maximumRotation: 360
                    minimumScale: 0.1
                    maximumScale: 10
                    grabPermissions: PointerHandler.CanTakeOverFromAnything // and never gonna give it up
                    onActiveChanged: if (active) photoFrame.z = ++flick.highestZ
                }

                DragHandler {
                    id: dragHandler
                    onActiveChanged: {
                        if (active)
                            photoFrame.z = ++flick.highestZ
                        else
                            anim.restart(centroid.velocity)
                    }
                }

Da PinchHandler innerhalb des Rechtecks deklariert ist, ist die Eigenschaft PinchHandler.target implizit so eingestellt, dass Pinch-Gesten das Rechteck manipulieren. Die Rotationseigenschaften legen fest, dass die Rahmen in alle Richtungen gedreht werden können, und die Skalierungseigenschaften legen fest, dass sie zwischen 0.1 und 10 skaliert werden können. Die Pinch-Geste funktioniert auch auf einem Touchscreen oder einem Multi-Touch-Touchpad. Durch das Transformieren des Rahmens wird sein Inhalt (das Bild) transformiert.

Die Eigenschaft DragHandler.target ist ebenfalls implizit gesetzt, so dass Sie ein Foto mit einem Finger auf einem Touchscreen oder Touchpad oder mit einer Maus ziehen können. Im Signalhandler von DragHandler( onActiveChanged ) wird der ausgewählte Fotorahmen nach oben verschoben, indem der Wert der Eigenschaft z erhöht wird (während die gemeinsame Eigenschaft highestZ den größten Wert von z enthält, der bisher verwendet wurde). Nach Beendigung des Ziehens wird eine Animation gestartet, damit sich das Bild eine Weile in dieselbe Richtung bewegt und dann langsamer wird und zum Stillstand kommt. Wenn Sie ein Foto über den Rand der Oberfläche hinaus "schleudern", dehnt sich die Oberfläche aus, um ihre neue Position einzunehmen. Sie können sich bewegen, um verschiedene Teile der Oberfläche über die ScrollView zu betrachten, die den Repeater und alle von ihm befüllten Fotorahmen enthält.

Da Sie zwei Fotos mit zwei Fingern über ihre DragHandler ziehen können und Sie auch ein PinchHandler mit zwei Fingern zusammenschieben können und sich Fotosammlungen übereinander stapeln, müssen wir grabPermissions so anpassen, dass PinchHandler Vorrang hat: Wenn die Zusammenschiebegeste beginnt, lässt sie nicht zu, dass die DragHandler die Touchpoint-Griffe wieder übernehmen.

Um das Beispiel auf Computern ohne Touch-Geräte interaktiver zu machen, fügen wir die HoverHandler hinzu, von der die obige border.color abhängt, sowie zwei WheelHandlers. Mit dem einen können Sie die Strg-Taste gedrückt halten und das Mausrad verwenden, um das Foto um den Mauszeiger zu drehen; mit dem anderen können Sie die Umschalttaste gedrückt halten und das Mausrad verwenden, um die Position unter dem Mauszeiger zu vergrößern oder zu verkleinern. In beiden Fällen wird das Foto auf die gleiche Weise angehoben, wie es in DragHandler der Fall ist:

                HoverHandler { id: mouse }

                WheelHandler {
                    acceptedModifiers: Qt.ControlModifier
                    property: "rotation"
                    onActiveChanged: if (active) photoFrame.z = ++flick.highestZ
                }

                WheelHandler {
                    acceptedModifiers: Qt.ShiftModifier
                    property: "scale"
                    onActiveChanged: if (active) photoFrame.z = ++flick.highestZ
                }

Beispielprojekt @ code.qt.io

Siehe auch QML-Anwendungen.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.