Sur cette page

Photo Surface

Une application QML pour les appareils tactiles qui utilise un Repeater avec un FolderListModel pour accéder au contenu d'un dossier, et un PinchHandler pour gérer les gestes de pincement sur le contenu récupéré.

Une collection de photos

Photo Surface montre comment utiliser un Repeater avec un FolderListModel et un FolderDialog pour accéder aux images d'un dossier sélectionné par un utilisateur. L'exemple montre également comment gérer le glissement, la rotation et le zoom par pincement à l'intérieur d'un même élément à l'aide des gestionnaires d'entréeQt Quick .

Tout le code de l'application est contenu dans un fichier QML, photosurface.qml. Le code JavaScript en ligne place, fait pivoter et met à l'échelle les images sur la surface de la photo.

Exécution de l'exemple

Pour exécuter l'exemple à partir de Qt Creatorouvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutoriel : Construire et exécuter.

Création de la fenêtre principale

Pour créer la fenêtre principale de l'application Photo Surface, utilisez le type QML Window comme élément racine. Il configure automatiquement la fenêtre pour une utilisation avec Qt Quick types graphiques :

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

Accès au contenu des dossiers

Utilisez un type QML Repeater avec le type FolderListModel pour afficher les images GIF, JPG et PNG situées dans un dossier (bien que main.cpp puisse étendre la liste des types d'images pris en charge) :

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

Importer le type FolderListModel:

import Qt.labs.folderlistmodel

Une boîte de dialogue FolderDialog permet aux utilisateurs de sélectionner le dossier qui contient les images :

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

Pour utiliser le type FolderDialog, ajoutez l'instruction d'importation suivante :

import QtQuick.Dialogs

La fonction folderDialog.open() ouvre la boîte de dialogue des dossiers à la fin du diaporama initial, à moins qu'un chemin d'accès au dossier n'ait été fourni en tant qu'argument de ligne de commande :

        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()
            }

Les utilisateurs peuvent également cliquer sur l'icône de la boîte de dialogue de dossier pour l'ouvrir. Ici, un type QML Image affiche l'icône. À l'intérieur du type Image, un TapHandler avec le gestionnaire de signal onTapped appelle la fonction folderDialog.open():

    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()
        }
    }

Affichage d'images sur la surface photo

Un Rectangle est utilisé comme délégué pour un Repeater afin de fournir un cadre pour chaque image que le FolderListModel trouve dans le dossier sélectionné. Les méthodes JavaScript Math() placent les cadres de manière aléatoire sur la surface de la photo, les font pivoter selon des angles aléatoires et mettent les images à l'échelle. La couleur de la bordure indique l'état de l'interaction :

            delegate: Rectangle {
                id: photoFrame
                required property date fileModified
                required property string fileName
                required property url fileUrl
                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
                }

Gestion des gestes de glissement et de pincement, et de la souris

Un DragHandler et un PinchHandler dans chaque cadre photo gèrent le glissement, le zoom par pincement et la rotation :

                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)
                    }
                }

Comme PinchHandler est déclaré à l'intérieur du rectangle, la propriété PinchHandler.target est implicitement définie de sorte que les gestes de pincement manipulent le rectangle. Les propriétés de rotation indiquent que les cadres peuvent être tournés sous tous les angles et les propriétés d'échelle indiquent qu'ils peuvent être mis à l'échelle entre 0.1 et 10. Le geste de pincement fonctionne aussi bien sur un écran tactile que sur un pavé tactile multi-touch. La transformation du cadre transforme son contenu (l'image).

La propriété DragHandler.target est également définie implicitement, de sorte que vous pouvez faire glisser une photo avec un doigt sur un écran tactile ou un pavé tactile, ou avec une souris. Dans le gestionnaire de signal onActiveChanged de DragHandler, le cadre de la photo sélectionnée s'élève vers le haut en augmentant la valeur de sa propriété z (tandis que la propriété partagée highestZ contient la plus grande valeur z qui a été utilisée jusqu'à présent). Lorsque le glissement se termine, une animation commence à le faire bouger dans la même direction pendant un certain temps, avant de ralentir et de s'arrêter. Si vous "projetez" une photo au-delà du bord de la surface, celle-ci s'agrandit pour s'adapter à sa nouvelle position. Vous pouvez vous déplacer pour visualiser différentes parties de la surface via le site ScrollView qui contient le répéteur et tous les cadres photo qu'il remplit.

Étant donné que vous pouvez faire glisser deux photos avec deux doigts via leurs DragHandlers, et que vous pouvez également pincer une PinchHandler avec deux doigts, et que les collections de photos ont tendance à s'empiler les unes sur les autres, vous devez ajuster grabPermissions de manière à ce que PinchHandler ait la priorité : lorsque le geste de pincement commence, il ne permet pas aux DragHandlers de reprendre la main sur les points de contact.

Pour rendre l'exemple plus interactif sur les ordinateurs dépourvus de dispositifs tactiles, ajoutez le HoverHandler dont dépend le border.color ci-dessus, ainsi que deux WheelHandlers. L'un vous permet de maintenir la touche Ctrl enfoncée et d'utiliser la molette de la souris pour faire tourner la photo autour du curseur de la souris ; l'autre vous permet de maintenir la touche Shift enfoncée et d'utiliser la molette de la souris pour effectuer un zoom avant ou arrière sur la position située sous le curseur. Ces deux options permettent également de relever la photo de la même manière que le fait le site DragHandler ci-dessus :

                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
                }

Fichiers sources

Exemple de projet @ code.qt.io

Voir aussi Applications QML et Qt Quick Exemples et tutoriels.

© 2026 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.