Sur cette page

Photo Surface

Une application QML pour 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 sélection 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 l’utilisateur. L’exemple montre également comment gérer le glissement, la rotation et le zoom par pincement au sein d’un même élément à l’aide des gestionnaires d’entrée «Qt Quick ».

Tout le code de l’application est contenu dans un seul fichier QML, photosurface.qml. Le code JavaScript intégré permet de placer, de faire pivoter et de redimensionner les images sur la surface de la photo.

Exécution de l’exemple

Vous pouvez exécuter l’exemple à partir de :

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 les 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éder au contenu d’un dossier

Utilisez un type QML ` Repeater ` associé à la classe ` FolderListModel ` pour afficher les images au format GIF, JPG et PNG situées dans un dossier (bien que le fichier `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
            }

Importez le type FolderListModel:

import Qt.labs.folderlistmodel

Un FolderDialog permet aux utilisateurs de sélectionner le dossier contenant les images :

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

Pour utiliser le type FolderDialog, ajoutez la déclaration d’importation suivante :

import QtQuick.Dialogs

La fonction ` folderDialog.open() ` ouvre la boîte de dialogue de fichier à la fin du diaporama initial, sauf si un chemin d’accès au dossier a été fourni en 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 des images sur la surface « Photo »

Un ` Rectangle ` est utilisé comme délégué d’un ` Repeater ` afin de fournir un cadre pour chaque image que la fonction ` FolderListModel ` trouve dans le dossier sélectionné. Les méthodes JavaScript ` Math() ` placent les cadres de manière aléatoire sur la surface photo, les font pivoter selon des angles aléatoires et redimensionnent les images. 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, ainsi que de la souris

Un objet ` DragHandler ` et un objet ` 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 l’ 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 agissent sur le Rectangle. Les propriétés de rotation spécifient que les cadres peuvent être pivotés selon n’importe quel angle, et les propriétés de mise à l’échelle indiquent qu’ils peuvent être redimensionnés entre 0.1 et 10. Le geste de pincement fonctionne aussi bien sur un écran tactile que sur un pavé tactile multipoint. La transformation du cadre entraîne celle de son contenu ( l’Image).

La propriété « DragHandler.target » est également définie implicitement, ce qui vous permet de faire glisser une photo avec un seul doigt sur un écran tactile ou un pavé tactile, ou à l’aide d’une souris. Dans le gestionnaire de signal « onActiveChanged » de l’ DragHandler, le cadre photo sélectionné remonte au premier plan en augmentant la valeur de sa propriété « z » (tandis que la propriété partagée « highestZ » conserve la plus grande valeur de « z » utilisée jusqu’à présent). Lorsque le glissement prend fin, une animation se déclenche pour maintenir le cadre dans la même direction pendant un court instant, avant qu’il ne ralentisse et ne s’immobilise. Si vous « projetez » une photo au-delà du bord de la surface, celle-ci s’étend pour s’adapter à sa nouvelle position. Vous pouvez vous déplacer pour visualiser différentes parties de la surface via l’ ScrollView, qui contient le Repeater et tous les cadres photo qu’il alimente.

Comme 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 l’ grabPermissions afin que l’ PinchHandler ait la priorité : lorsque le geste de pincement commence, il ne permet pas aux DragHandlers de reprendre le contrôle des points de contact.

Pour rendre l’exemple plus interactif sur les ordinateurs dépourvus de périphériques tactiles, ajoutez l’ HoverHandler dont dépend la propriété 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 pivoter la photo autour du curseur ; l’autre vous permet de maintenir la touche Maj enfoncée et d’utiliser la molette de la souris pour zoomer ou dézoomer sur la position sous le curseur. Ces deux éléments font également apparaître la photo de la même manière que le ` 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 sur code.qt.io

Voir également Applications QML et Exemples et tutoriels d’Qt Quick .

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