web-dev-qa-db-fra.com

Comment obtenir un composant délégué instancié à partir d'un GridView ou d'un ListView dans QML

Mon énigme, exprimée de manière générale, est la suivante: grâce à une action en dehors de GridView, je souhaite déterminer les coordonnées d’un élément délégué particulier dans GridView en se basant uniquement sur un élément de modèle ou un index sélectionné auparavant.

J'ai un GridView avec un certain nombre d'éléments dans le modèle. Le délégué de GridView crée une vue miniature de chaque élément. Lorsque vous cliquez dessus, une vue détaillée de l’élément en plein écran apparaît. Je voudrais une transition de Nice qui montre la vignette en train de sortir de sa place dans GridView et, lorsque la vue détaillée est supprimée, de revenir à GridView.

Le truc, c'est que la vue détaillée est elle-même un délégué d'un ListView, ce qui vous permet de feuilleter entre les vues détaillées, écran par écran. Cela signifie une solution qui redimensionne simplement l'élément délégué de GridView ou quelque chose ne fonctionnera pas. De plus, comme vous pouvez rechercher une page dans n'importe quel élément de la liste, le retour dans la grille doit être effectué uniquement en fonction des informations disponibles dans le modèle ou de l'index de celui-ci (par exemple, je ne peux pas stocker les coordonnées de la souris MouseArea utilisée pour lancer la vue détaillée ou quelque chose).

L'animation d'extension est assez facile, car l'élément délégué a un MouseArea pour le gestionnaire de clics qui connaît son propre emplacement, de sorte qu'il peut être transmis à la fonction qui démarre l'animation. C'est l'inverse que je ne peux pas comprendre: à partir de l'élément/index du modèle dans ListView, comment puis-je déterminer les coordonnées de l'élément associé dans GridView?

Je ne trouve rien dans la documentation qui semble vous permettre d'avoir accès à une instance d'élément délégué à partir d'une instance d'élément de modèle ou même d'un index. Le GridView a indexAt() qui renvoie l'index en fonction des coordonnées. Je pense que je pourrais me débrouiller avec l'inverse, mais cela ne semble pas exister.

Voici un exemple plus concret. Excuses pour la longueur; C'est l'exemple de code le plus court que je pourrais trouver qui décrit avec précision mon problème:

import QtQuick 1.1

Item {
    id: window
    width: 400
    height: 200

    state: "summary"
    states: [
        State { name: "summary"; },
        State { name: "details"; }
    ]
    transitions: [
        Transition { from: "summary"; to: "details";
            SequentialAnimation {
                PropertyAction { target: animationRect; property: "visible"; value: true; }
                ParallelAnimation {
                    NumberAnimation { target: animationRect; properties: "x,y"; to: 0; duration: 200; }
                    NumberAnimation { target: animationRect; property: "width"; to: 400; duration: 200; }
                    NumberAnimation { target: animationRect; property: "height"; to: 200; duration: 200; }
                }
                PropertyAction { target: detailsView; property: "visible"; value: true; }
                PropertyAction { target: summaryView; property: "visible"; value: false; }
                PropertyAction { target: animationRect; property: "visible"; value: false; }
            }
        },
        Transition { from: "details"; to: "summary";
            SequentialAnimation {
                PropertyAction { target: summaryView; property: "visible"; value: true; }

                // How to animate animationRect back down to the correct item?

                PropertyAction { target: detailsView; property: "visible"; value: false; }
            }
        }
    ]

    Rectangle {
        id: animationRect
        z: 1
        color: "gray"
        visible: false

        function positionOverSummary(summaryRect) {
            x = summaryRect.x; y = summaryRect.y;
            width = summaryRect.width; height = summaryRect.height;
        }
    }

    ListModel {
        id: data
        ListElement { summary: "Item 1"; description: "Lorem ipsum..."; }
        ListElement { summary: "Item 2"; description: "Blah blah..."; }
        ListElement { summary: "Item 3"; description: "Hurf burf..."; }
    }

    GridView {
        id: summaryView
        anchors.fill: parent
        cellWidth: 100
        cellHeight: 100

        model: data
        delegate: Rectangle {
            color: "lightgray"
            width: 95; height: 95;

            Text { text: summary; }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    var delegateRect = mapToItem(window, x, y);
                    delegateRect.width = width; delegateRect.height = height;

                    animationRect.positionOverSummary(delegateRect);
                    detailsView.positionViewAtIndex(index, ListView.Beginning);
                    window.state = "details";
                }
            }
        }
    }

    ListView {
        id: detailsView
        anchors.fill: parent
        visible: false
        orientation: ListView.Horizontal
        snapMode: ListView.SnapOneItem

        model: data
        delegate: Rectangle {
            color: "gray"
            width: 400; height: 200;

            Column {
                Text { text: summary; }
                Text { text: description; }
            }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    // How do I get the coordinates to where animationRect should return?

                    summaryView.positionViewAtIndex(index, GridView.Visible);
                    window.state = "summary";
                }
            }
        }
    }
}

Des idées? Il est possible que je ne fasse que mal. Si ce que j'essaie de faire spécifiquement est impossible, y a-t-il un autre moyen de structurer cela? Merci!


Edit: Quelques idées que j'ai eues (dont aucune, à mon avis, n'est faisable):

  • Conservez une liste de tous les éléments de délégué créés à l'aide de Component.onCompleted et Component.onDestruction. Pour être utile, il doit s'agir d'une carte d'élément de modèle ou d'un index => d'élément délégué. Le problème est que les types de base docs (en particulier variante ) semblent indiquer que ce type de carte est impossible à créer en QML pur. Il semble donc que cela signifierait créer cette carte en tant que classe C++ et l'utiliser dans QML avec onCompleted/onDestruction dans le composant délégué pour la maintenir à jour. Cela semble un peu dangereux et lourd pour quelque chose qui devrait être simple.

  • Cet article de liste de diffusion semble indiquer que la propriété contentItem de Flickable peut être utilisée pour énumérer les éléments délégués. Ensuite, j'ai trouvé ce post l'appelant comme une mauvaise pratique. Je suis encore à la recherche, mais je suis sceptique, ce sera une solution légitime. Cela semble trop compliqué pour fonctionner de manière fiable.

C'est tout ce que j'ai jusqu'à présent.

16
chazomaticus

Après quelques recherches, il s'avère que contentItem tient les délégués instanciés pour un scintillable. Comme je l'ai dit plus haut, je suis sceptique quant au fait qu'il s'agisse bien de la meilleure façon de procéder, voire d'une bonne période, mais cela semble fonctionner. Je posterai le code complet de cette solution hacky ci-dessous, mais j'espère toujours qu'il existe une meilleure solution. Le bit vraiment important est la nouvelle fonction getDelegateInstanceAt() dans GridView.

import QtQuick 1.1

Item {
    id: window
    width: 400
    height: 200

    state: "summary"
    states: [
        State { name: "summary"; },
        State { name: "details"; }
    ]
    transitions: [
        Transition { from: "summary"; to: "details";
            SequentialAnimation {
                PropertyAction { target: animationRect; property: "visible"; value: true; }
                ParallelAnimation {
                    NumberAnimation { target: animationRect; properties: "x,y"; to: 0; duration: 200; }
                    NumberAnimation { target: animationRect; property: "width"; to: 400; duration: 200; }
                    NumberAnimation { target: animationRect; property: "height"; to: 200; duration: 200; }
                }
                PropertyAction { target: detailsView; property: "visible"; value: true; }
                PropertyAction { target: summaryView; property: "visible"; value: false; }
                PropertyAction { target: animationRect; property: "visible"; value: false; }
            }
        },
        Transition { from: "details"; to: "summary";
            id: shrinkTransition
            property variant destRect: {"x": 0, "y": 0, "width": 0, "height": 0}

            SequentialAnimation {
                PropertyAction { target: summaryView; property: "visible"; value: true; }
                PropertyAction { target: animationRect; property: "visible"; value: true; }
                PropertyAction { target: detailsView; property: "visible"; value: false; }
                ParallelAnimation {
                    NumberAnimation { target: animationRect; property: "x"; to: shrinkTransition.destRect.x; duration: 200; }
                    NumberAnimation { target: animationRect; property: "y"; to: shrinkTransition.destRect.y; duration: 200; }
                    NumberAnimation { target: animationRect; property: "width"; to: shrinkTransition.destRect.width; duration: 200; }
                    NumberAnimation { target: animationRect; property: "height"; to: shrinkTransition.destRect.height; duration: 200; }
                }
                PropertyAction { target: animationRect; property: "visible"; value: false; }
            }
        }
    ]

    Rectangle {
        id: animationRect
        z: 1
        color: "gray"
        visible: false

        function positionOverSummary(summaryRect) {
            x = summaryRect.x; y = summaryRect.y;
            width = summaryRect.width; height = summaryRect.height;
        }

        function prepareForShrinkingTo(summaryRect) {
            x = 0; y = 0;
            width = 400; height = 200;
            shrinkTransition.destRect = summaryRect;
        }
    }

    ListModel {
        id: data
        ListElement { summary: "Item 1"; description: "Lorem ipsum..."; }
        ListElement { summary: "Item 2"; description: "Blah blah..."; }
        ListElement { summary: "Item 3"; description: "Hurf burf..."; }
    }

    GridView {
        id: summaryView
        anchors.fill: parent
        cellWidth: 100
        cellHeight: 100

        model: data
        delegate: Rectangle {
            // These are needed for getDelegateInstanceAt() below.
            objectName: "summaryDelegate"
            property int index: model.index

            color: "lightgray"
            width: 95; height: 95;

            Text { text: summary; }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    var delegateRect = mapToItem(window, x, y);
                    delegateRect.width = width; delegateRect.height = height;

                    animationRect.positionOverSummary(delegateRect);
                    detailsView.positionViewAtIndex(index, ListView.Beginning);
                    window.state = "details";
                }
            }
        }

        // Uses black magic to hunt for the delegate instance with the given
        // index.  Returns undefined if there's no currently instantiated
        // delegate with that index.
        function getDelegateInstanceAt(index) {
            for(var i = 0; i < contentItem.children.length; ++i) {
                var item = contentItem.children[i];
                // We have to check for the specific objectName we gave our
                // delegates above, since we also get some items that are not
                // our delegates here.
                if (item.objectName == "summaryDelegate" && item.index == index)
                    return item;
            }
            return undefined;
        }
    }

    ListView {
        id: detailsView
        anchors.fill: parent
        visible: false
        orientation: ListView.Horizontal
        snapMode: ListView.SnapOneItem

        model: data
        delegate: Rectangle {
            color: "gray"
            width: 400; height: 200;

            Column {
                Text { text: summary; }
                Text { text: description; }
            }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    summaryView.positionViewAtIndex(index, GridView.Visible);

                    var delegateInstance = summaryView.getDelegateInstanceAt(index);
                    var delegateRect = window.mapFromItem(summaryView,
                        delegateInstance.x - summaryView.contentX,
                        delegateInstance.y - summaryView.contentY
                    );
                    delegateRect.width = delegateInstance.width;
                    delegateRect.height = delegateInstance.height;

                    animationRect.prepareForShrinkingTo(delegateRect);
                    window.state = "summary";
                }
            }
        }
    }
}

S'il vous plaît dites-moi qu'il existe un moyen plus robuste!

9
chazomaticus

Juste au cas où quelqu'un voudrait savoir comment accomplir cela en C++, voici un extrait rapide:

//get the reference to GridView somehow (QObject *obj) (omitted for brevity)
QQuickItem *x = qobject_cast<QQuickItem *>(obj);
if (x->property("contentItem").isValid()) {
    QQuickItem *o = qvariant_cast<QQuickItem *>(x->property("contentItem"));
    qDebug() << "Extracting content item " << o->metaObject()->className() << " from " << x->metaObject()->className();
    qDebug() << "item has ch count " << o->childItems().count();
}

Il est important de noter (et la principale raison de la publication de cette information) que l'accès à children() à partir de QQuickItem invoquera la méthode QObject::children(), qui ne renvoie pas nécessairement les mêmes objets que QQuickItem::childItems(). Il y a quatre jours... :)

2
tadelv

Pour le type QML ListView, la fonction simple suivante peut obtenir l'instance de délégué à un index spécifique:

function getDelegateInstanceAt(index) {
    return contentItem.children[index];
}

Voici un exemple de test QML qui utilise la fonction ci-dessus, avec contrôle supplémentaire des erreurs et code de journalisation, basé sur Qt 5.5:

import QtQuick 2.0
import QtQuick.Controls 1.2

Rectangle {
    width: 400
    height: 200

    ListView { id: fruitView
        width: parent.width
        height: parent.height / 2
        anchors.left: parent.left
        anchors.top: parent.top

        model: fruitModel

        delegate: TextInput {
            text: fruit_name
        }

        // Function to get the delegate instance at a specific index:
        // =========================================================
        function getDelegateInstanceAt(index) {
            console.log("D/getDelegateInstanceAt[" + index + "]");

            var len = contentItem.children.length;
            console.log("V/getDelegateInstanceAt: len[" + len + "]");

            if(len > 0 && index > -1 && index < len) {
                return contentItem.children[index];
            } else {
                console.log("E/getDelegateInstanceAt: index[" + index + "] is invalid w.r.t len[" + len + "]");
                return undefined;
            }
        }
    }

    Rectangle {
        width: parent.width
        height: parent.height / 2
        anchors.left: parent.left
        anchors.bottom: parent.bottom

        Button {
            anchors.centerIn: parent
            text: "getDelegateInstanceAt(1)"

            onClicked: {
                // Code to test function getDelegateInstanceAt():
                var index = 1;
                var myDelegateItem1 = fruitView.getDelegateInstanceAt(index);
                if(myDelegateItem1) {
                    console.log("I/onClicked: found item at index[" + index + "] fruit_name[" + myDelegateItem1.text + "]"); // Should see: fruit_name[Banana_1]
                } else {
                    console.log("E/onClicked: item at index[" + index + "] is not found.");
                }
            }
        }
    }

    ListModel { id: fruitModel
        ListElement { fruit_name: "Apple_0" }
        ListElement { fruit_name: "Banana_1" }
        ListElement { fruit_name: "Cherry_2" }
    }
}

Bien que la fonction ci-dessus fonctionne bien avec ces objets simples "fruitModel" et "fruitView", il peut être nécessaire de l'améliorer davantage pour traiter des instances plus complexes de ListModel et de ListView.

0
jonathanzh

Après seulement quelques mois de programmation QML, j'ai proposé cette solution, similaire à celle de l'OP, mais je la posterai quand même dans l'espoir que cela puisse aider d'autres personnes à résoudre ce problème.

Cet exemple comporte essentiellement trois composants: un GridView pour afficher les composants visuels (visualiseur), un Item qui contient une liste de QObjects (données) et un Component qui entoure un Rectangle de couleur (délégué).

Le visualiseur prend les données et les montre en créant des délégués. Suivant ce schéma, le moyen le plus simple de modifier le contenu d'un délégué consiste à accéder à ses données. Pour accéder aux données d'un délégué, vous devez d'abord atteindre l'objet listmodel auquel, dans cet exemple, vous pouvez accéder via grid.children[1] (vous ne savez pas pourquoi ce n'est pas à grid.children[0], mais il ne s'agit que d'un détail), et vous pouvez enfin accéder au bon délégué via grid.children[1].list_model[index]. Cela peut être facilement emballé dans une fonction getChild().

La recommandation finale est de donner une objectName significative à presque tout depuis le début du développement. Cette pratique facilite beaucoup le débogage et, même si c'est mauvais, permet d'accéder aux données depuis C++.

GridView
{
    id:                 grid
    objectName:         "grid"

    cellWidth:          50
    cellHeight:         50

    anchors.left:       parent.left
    anchors.top:        parent.top
    anchors.margins:    100

    width:              100
    height:             100

    model:              listmodel.list_model

    delegate:           delegate
    focus:              false
    interactive:        false

    function getChild(index)
    {
        var listmodel = grid.children[1].list_model
        var elem = listmodel[index]

        return elem
    }

    Component.onCompleted:
    {
        for (var idx = 0; idx < grid.children.length; idx++)
        {
            console.log("grid.children[" + idx + "].objectName: " + grid.children[idx].objectName)
        }

        var elem = getChild(2)
        elem.model_text += " mod"
        elem.model_color = "slateblue"
    }

    Item
    {
        id: listmodel
        objectName: "listmodel"

        // http://www.w3.org/TR/SVG/types.html#ColorKeywords

        property list<QtObject> list_model:
        [
            QtObject
            {
                objectName:                             "rectmodel" + model_idx
                property int        model_idx:          1
                property string     model_text:         "R" + model_idx
                property color      model_color:        "crimson"
                property bool       model_visible:      true
            },
            QtObject
            {
                objectName:                             "rectmodel" + model_idx
                property int        model_idx:          2
                property string     model_text:         "R" + model_idx
                property color      model_color:        "lawngreen"
                property bool       model_visible:      true
            },
            QtObject
            {
                objectName:                             "rectmodel" + model_idx
                property int        model_idx:          3
                property string     model_text:         "R" + model_idx
                property color      model_color:        "steelblue"
                property bool       model_visible:      true
            },
            QtObject
            {
                objectName:                             "rectmodel" + model_idx
                property int        model_idx:          4
                property string     model_text:         "R" + model_idx
                property color      model_color:        "gold"
                property bool       model_visible:      true
            }
        ]
    }

    Component
    {
        id:         delegate

        Rectangle
        {
            id:                     delegaterect
            objectName:             "delegaterect"

            width:                  grid.cellWidth
            height:                 grid.cellHeight

            color:                  model_color
            visible:                model_visible

            Component.onCompleted:
            {
                console.log("delegaterect.children[0].objectName: " + delegaterect.children[0].objectName + " - " + delegaterect.children[0].text)
            }

            Text
            {
                id:                 delegatetext
                objectName:         "delegatetext"
                anchors.centerIn:   parent
                text:               qsTr(model_text)
            }
        }
    }
}
0
Avio