このページについて

Photo Surface

タッチデバイス向けのQMLアプリで、FolderListModel を備えたRepeaterを使用してフォルダ内のコンテンツにアクセスし、PinchHandler を用いて取得したコンテンツに対するピンチジェスチャーを処理します。

写真のコレクション

「Photo Surface」は、Repeater をFolderListModel およびFolderDialog と組み合わせて使用し、ユーザーが選択したフォルダ内の画像にアクセスする方法を示しています。また、この例では、Qt Quick のInput Handlerを使用して、同一のアイテム内でのドラッグ、回転、ピンチズームを処理する方法も示しています。

アプリのコードはすべて、1つのQMLファイル(photosurface.qml )にまとめられています。インラインのJavaScriptコードにより、画像がフォトサーフェス上に配置、回転、拡大縮小されます。

例の動作確認

この例は、以下の手順で実行できます。

メインウィンドウの作成

Photo Surface アプリのメインウィンドウを作成するには、Window QML タイプをルートアイテムとして使用します。これにより、以下のグラフィカルタイプで使用するためのウィンドウが自動的に設定されます Qt Quick グラフィカルなタイプに対応したウィンドウが自動的に設定されます:

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

フォルダの内容へのアクセス

Repeater QML タイプをFolderListModel と組み合わせて使用し、フォルダ内にある GIF、JPG、PNG 画像を表示します(ただし、main.cpp によってサポートされる画像形式のリストが拡張される可能性があります):

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

FolderListModel 型をインポートします:

import Qt.labs.folderlistmodel

FolderDialogを使用すると、ユーザーは画像が格納されているフォルダを選択できます:

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

FolderDialog タイプを使用するには、次のインポート文を追加します:

import QtQuick.Dialogs

folderDialog.open() 関数は、コマンドライン引数としてフォルダパスが指定されていない限り、最初のスライドショーが終了したときにファイルダイアログを開きます:

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

ユーザーは、フォルダダイアログのアイコンをクリックしてダイアログを開くこともできます。ここでは、ImageQML型がそのアイコンを表示しています。Image型内部では、onTapped シグナルハンドラを持つTapHandler が、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()
        }
    }

Photoサーフェスへの画像の表示

Rectangle は、Repeater のデリゲートとして使用され、FolderListModel が選択されたフォルダ内で検出した各画像にフレームを提供します。JavaScriptのMath() メソッドは、フレームをフォトサーフェス上にランダムに配置し、ランダムな角度で回転させ、画像の拡大縮小を行います。枠線の色は、インタラクションの状態を示します:

            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
                }

ドラッグおよびピンチジェスチャー、およびマウスの処理

各写真フレーム内の `DragHandler ` および `PinchHandler ` が、ドラッグ、ピンチズーム、および回転を処理します:

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

PinchHandler がRectangle内部で宣言されているため、PinchHandler.target プロパティは暗黙的に設定され、ピンチジェスチャーによってRectangleを操作できるようになります。rotationプロパティにより、フレームはあらゆる角度に回転可能となり、scaleプロパティにより、0.1 から10 の範囲で拡大縮小が可能となります。ピンチジェスチャーは、タッチスクリーンでもマルチタッチ対応のタッチパッドでも同様に正常に動作します。 フレームを変形させると、その内容(Image)も変形します。

DragHandler.target プロパティも暗黙的に設定されているため、タッチスクリーンやタッチパッドでは1本の指で、マウスでも写真をドラッグすることができます。DragHandler のonActiveChanged シグナルハンドラでは、選択された写真フレームのz プロパティの値を増やすことで、そのフレームを最前面に表示します(一方、共有プロパティであるhighestZ には、これまでに使用された最大のz 値が保持されます)。 ドラッグが終了すると、アニメーションが開始され、しばらくの間同じ方向に動き続け、徐々に減速して停止します。写真をサーフェスの端を越えて「フリック」すると、サーフェスは新しい位置に合わせて拡張されます。ScrollView (Repeaterおよびそこに配置されたすべての写真フレームを含む)を操作することで、サーフェスのさまざまな部分を閲覧できます。

2本の指を使ってDragHandlerを介して2枚の写真をドラッグしたり、2本の指でPinchHandler をピンチしたりできるほか、写真のコレクションは互いに重なり合う傾向があるため、grabPermissions を調整してPinchHandler に優先順位を持たせる必要があります。つまり、ピンチジェスチャーが開始された際、DragHandlerがタッチポイントのグリップを再び奪い取れないようにします。

タッチデバイスがないコンピュータでもこの例をよりインタラクティブにするには、上記の border.color に依存するHoverHandler と、2つのWheelHandlers を追加します。 1つは、Ctrlキーを押しながらマウスホイールを回すと、マウスカーソルの周囲で写真を回転させることができます。もう1つは、Shiftキーを押しながらマウスホイールを回すと、カーソルの位置を拡大または縮小できます。これら2つとも、前述のDragHandler と同様に写真を浮き上がらせます:

                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
                }

ソースファイル

code.qt.io のサンプルプロジェクト

「QMLアプリケーション」および「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.