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

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