web-dev-qa-db-ja.com

QMLのGridViewまたはListViewからインスタンス化されたデリゲートコンポーネントを取得する方法

私の難問は、一般的には次のとおりです。GridViewの外部のアクションを通じて、以前に選択された特定のモデルアイテムまたはインデックスのみに基づいて、GridView内の特定のデリゲートアイテムの座標を把握したいと思います。

モデルに多数のアイテムがあるGridViewがあります。 GridViewのデリゲートは、各アイテムのサムネイルビューを作成します。クリックすると、アイテムの詳細なフルスクリーンビューが表示されます。サムネイルがGridViewのその場所から拡大して表示され、詳細ビューが閉じられると、縮小して元のGridViewに戻る、素敵な移行が必要です。

コツは、詳細ビュー自体がListViewのデリゲートであるため、一度に1つの画面で詳細ビュー間をページングできることです。これは、GridViewのデリゲート項目のサイズを変更するだけのソリューションでは、機能しないことを意味します。また、ListView内の任意のアイテムにページングできるため、GridViewに戻るには、モデルまたはモデルアイテムのインデックスで使用可能な情報のみに基づいて行う必要があります(たとえば、詳細ビューか何か)。

デリゲートアイテムには、独自の配置を知っているクリックハンドラーのMouseAreaがあり、アニメーションを開始する関数に渡すことができるので、拡張アニメーションはかなり簡単です。それは私が理解できない逆です:ListViewのモデルアイテム/インデックスから、GridViewの関連アイテムの座標をどのように把握するのですか?

モデルアイテムインスタンスまたはインデックスからデリゲートアイテムインスタンスにアクセスできるように見えるドキュメントには何も見つかりません。 GridViewには、座標に基づいてインデックスを返すindexAt()があります。逆はうまくいくと思いますが、実在しないようです。

これはより具体的な例です。長さの謝罪;これは、私の問題を正確に説明する、思いつくことができる最も短いコード例です。

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";
                }
            }
        }
    }
}

何か案は?これを間違った方法で行っている可能性があります。私が特にやろうとしていることが不可能である場合、これを構築する方法は他にありますか?ありがとう!


編集:私が持っているいくつかのアイデア(どれも実現可能ではないと私は信じています):

  • Component.onCompletedおよびComponent.onDestructionを使用して、作成されたすべてのデリゲートアイテムのリストを保持します。役立つためには、モデルアイテムまたはインデックス=>デリゲートアイテムのマップである必要があります。問題は、 基本タイプのドキュメント (特に variant )が、この種のマップを純粋なQMLで作成することが不可能であることを示しているようです。したがって、これはこのマップをC++クラスとして作成し、それをQMLでonCompleted/onDestructionとともにデリゲートコンポーネント内で使用して最新の状態に保つことを意味すると思われます。シンプルである必要があるため、少し危険で強引なようです。

  • このメーリングリストの投稿 は、FlickableのcontentItemプロパティを使用して、デリゲートアイテムを列挙できることを示しているようです。それから私は この投稿 をそれを悪い習慣として呼び出すことを見つけました。まだ調査中ですが、これが正当な解決策になるかどうかは疑問です。信頼性の高い方法で動作するにはあまりにもハッキリすぎるようです。

私がこれまでに得たのはそれだけです。

17
chazomaticus

調査の結果、contentItemはFlickableのインスタンス化されたデリゲートを保持していることがわかりました。上で述べたように、これが本当にこれを行うための最良の方法、または良い方法の期間でさえあるかどうか私は懐疑的ですが、それはうまくいくようです。このハッキーなソリューションの完全なコードを以下に掲載しますが、もっと良い方法があることを願っています。本当に重要なのは、GridViewの新しいgetDelegateInstanceAt()関数です。

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";
                }
            }
        }
    }
}

より堅牢な方法があることを教えてください!

11
chazomaticus

C++でこれを実現する方法に興味がある人のために、簡単なスニペットを次に示します。

_//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();
}
_

QQuickItemからchildren()にアクセスするとQObject::children()メソッドが呼び出され、必ずしも同じオブジェクトを返さないことに注意してください(これを投稿する主な理由) QQuickItem::childItems()として。多分誰かがこれが便利だと思うかもしれません、それは確かに4日前に私を助けるでしょう... :)

2
tadelv

QMLタイプのListViewの場合、次の単純な関数で、特定のインデックスにあるデリゲートインスタンスを取得できます。

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

以下は、Qt 5.5に基づいて、エラーチェックとロギングコードを追加した、上記の関数を使用するQMLテストの例です。

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

上記の関数は、これらの単純な「fruitModel」および「fruitView」オブジェクトでうまく機能しますが、より複雑なListModelおよびListViewインスタンスを処理する場合は、さらに拡張する必要がある場合があります。

0
jonathanzh

QMLプログラミングのほんの数か月後に、OPの1つに似たこのソリューションを思い付きましたが、他の人がこのことを整理するのに役立つことを期待して、とにかくそれを投稿します。

この例には基本的に3つのコンポーネントがあります。ビジュアルコンポーネント(ビューア)を表示するGridViewItem(データ)のリストを含むQObjects、およびComponentは、色付きのRectangle(デリゲート)を囲みます。

ビューアはデータを受け取り、デリゲートを作成することを示します。このパターンに従って、デリゲートのコンテンツを変更する最も簡単な方法は、そのデータにアクセスすることです。デリゲートのデータにアクセスするには、最初にlistmodelオブジェクトに到達する必要があります。この例では、_grid.children[1]_を介してアクセスできます(理由は_grid.children[0]_にない理由はわかりませんが、これはは単なる詳細です)、最後に_grid.children[1].list_model[index]_を介して適切なデリゲートにアクセスします。これは、getChild()関数内に簡単にパッケージ化できます。

最後の推奨事項は、開発の開始以来、意味のあるobjectNameをほぼすべてに与えることです。これにより、デバッグが大幅に容易になり、たとえそれが悪であっても、_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