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

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 :
- Qt Creator
Ouvrez le mode « Welcome » et sélectionnez l’exemple à l’adresse Examples. Pour plus d’informations, consultez Qt Creator: Tutoriel : Compiler et exécuter.
- Qt Extension for Visual Studio Code
Exécutez la commande « Qt: Open Qt examples » à partir de Command Palette, puis sélectionnez l’exemple dans la liste. Pour plus d’informations, consultez Qt Extension for Visual Studio Code: Tutoriel : Compiler 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 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.cppAccé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.folderlistmodelUn 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.DialogsLa 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
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.