简体   繁体   English

如何从QML中的GridView或ListView获取实例化的委托组件

[英]How to get an instantiated delegate component from a GridView or ListView in QML

My conundrum, phrased generally, is: through some action outside the GridView, I want to figure out the coordinates of a particular delegate item in the GridView based only on a particular model item or index selected before. 我的难题一般来说是:通过GridView之外的一些动作,我想根据之前选择的特定模型项或索引来计算GridView中特定委托项的坐标。

I've got a GridView with a number of items in the model. 我有一个GridView,其中有许多项目在模型中。 The GridView's delegate creates a thumbnail view of each item. GridView的委托创建每个项目的缩略图视图。 When clicked, it brings up a detailed, fullscreen view of the item. 单击时,它会显示项目的详细全屏视图。 I'd like a nice transition that shows the thumbnail expanding out from its place in the GridView, and when the detailed view is dismissed, shrinking back down into the GridView in place. 我想要一个很好的转换,它显示缩略图从GridView中的位置扩展出来,当详细视图被解除时,缩小回到GridView中。

The trick is, the detailed view is itself a delegate of a ListView so you can page between detailed views one screen at a time. 诀窍是,详细视图本身就是ListView的委托,因此您可以一次在一个屏幕之间在详细视图之间进行分页。 This means a solution that simply resizes the GridView's delegate item or something won't work. 这意味着只需调整GridView的委托项目大小或某些内容的解决方案将无效。 Also, since you can page to any item in the ListView, returning to the GridView has to be done based only on information available in the model or the model item's index (eg I can't store the coords of the MouseArea used to launch the detailed view or something). 此外,由于您可以寻呼到ListView中的任何项目,因此必须仅基于模型中可用的信息或模型项目的索引来返回到GridView(例如,我无法存储用于启动的模拟项目的鼠标的坐标。详细的观点或东西)。

The expansion animation is fairly easy, as the delegate item has a MouseArea for the click handler that knows its own placement, so that can be passed out to the function that starts the animation. 扩展动画相当简单,因为委托项具有一个用于知道其自身位置的单击处理程序的MouseArea,因此可以将其传递给启动动画的函数。 It's the reverse I can't figure out: from the model item/index in the ListView, how do I figure out the coordinates of the related item in the GridView? 这是我无法弄清楚的反过来:从ListView中的模型项/索引,我如何计算GridView中相关项的坐标?

I can't find anything in the docs that would seem to let you have access to a delegate item instance from a model item instance or even an index. 我在文档中找不到任何似乎可以让您从模型项实例甚至索引访问委托项实例的内容。 The GridView has indexAt() that returns the index based on coordinates. GridView具有indexAt() ,它根据坐标返回索引。 I think I could make do with the reverse, but it doesn't appear to exist. 我想我可以反过来做,但它似乎不存在。

Here's a more concrete example. 这是一个更具体的例子。 Apologies for the length; 道歉; this is the shortest example code I could come up with that accurately describes my problem: 这是我能想出的最准确的代码,它准确地描述了我的问题:

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

Any ideas? 有任何想法吗? It's possible I'm just going about this the wrong way. 我可能只是以错误的方式解决这个问题。 If what I'm trying to do specifically is impossible, is there some other way I should be architecting this? 如果我想要做的具体是不可能的,还有其他方法我应该设计这个吗? Thanks! 谢谢!


Edit: Some ideas I've had (none of which I believe are feasible): 编辑:我有过的一些想法(我认为没有一个是可行的):

  • Keep a list of all the delegate items created, using Component.onCompleted and Component.onDestruction . 使用Component.onCompletedComponent.onDestruction保留创建的所有委托项的列表。 To be useful, it should be a map of model item or index => delegate item. 为了有用,它应该是模型项或index =>委托项的映射。 Trouble is, the basic types docs (especially variant ) seem to indicate that this kind of map is impossible to create in pure QML. 麻烦的是, 基本类型文档 (特别是变体 )似乎表明这种地图不可能用纯QML创建。 So it sounds like this would mean creating this map as a C++ class and using that in QML with onCompleted / onDestruction within the delegate component to keep it up to date. 所以听起来这意味着将这个地图创建为C ++类,并在QML中使用委托组件中的onCompleted / onDestruction ,以使其保持最新。 Seems a little dangerous and heavy-handed for something that should be simple. 对于应该简单的事情来说,似乎有点危险和苛刻。

  • This mailing list post seems to indicate that Flickable's contentItem property can be used to enumerate the delegate items. 此邮件列表帖子似乎表明Flickable的contentItem属性可用于枚举委托项。 Then I found this post calling that out as bad practice. 然后我发现这篇帖子称这是一种不好的做法。 I'm still looking into it, but I'm skeptical this will be a legitimate solution. 我仍在调查,但我怀疑这将是一个合法的解决方案。 Seems way too hacky to work reliably. 看起来太可靠了,无法可靠地工作。

That's all I've got so far. 这就是我到目前为止所做的一切。

After some investigation, it turns out that contentItem does hold the instantiated delegates for a Flickable. 经过一些调查后,事实证明contentItem确实为Flickable保存了实例化的委托。 As I said above, I'm skeptical that this is really the best way to do this, or even a good way period, but it does seem to work. 正如我上面所说,我怀疑这是真正做到这一点的最佳方式,甚至是一个好的方式,但它似乎确实有效。 I'll post the full code for this hacky solution below, but I'm still hoping there's a better way. 我将在下面发布这个hacky解决方案的完整代码,但我仍然希望有更好的方法。 The really important bit is the new getDelegateInstanceAt() function in the GridView. 非常重要的一点是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";
                }
            }
        }
    }
}

Please tell me there's a more robust way! 请告诉我有一个更健壮的方式!

Just in case anyone is interested into how to accomplish this in C++, here is a quick snippet: 如果有人对如何在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();
}

It is important to note (and the main reason for posting this), that accessing children() from QQuickItem will invoke the QObject::children() method, which does not necessarily return same objects as QQuickItem::childItems() . 重要的是要注意(以及发布此内容的主要原因),从QQuickItem访问children()将调用QObject::children()方法,该方法不一定返回与QQuickItem::childItems()相同的对象。 Maybe someone finds this useful, it would surely help me four days ago... :) 也许有人发现这很有用,它肯定会在四天前帮助我... :)

After just a few months of QML programming I've come up with this solution, similar to the one of the OP, but I'll post it anyway in the hope that it could help other people sorting this thing out. 经过短短几个月的QML编程,我已经提出了这个解决方案,类似于OP的解决方案,但无论如何我都会发布它,希望它可以帮助其他人整理这个东西。

There are basically three components in this example: a GridView to show visual components (viewer), an Item that contains a list of QObjects (data) and a Component that enclose a colored Rectangle (delegate). 在这个例子中基本上有三个组件:一个显示可视组件的GridView (查看器),一个包含QObjects (数据)列表的Item和一个包含彩色Rectangle (委托)的Component

The viewer takes the data and shows it creating delegates. 查看器获取数据并显示它创建委托。 Following this pattern, the easiest way to change the content of a delegate, is to access its data. 遵循此模式,更改委托内容的最简单方法是访问其数据。 To access the data of a delegate, one must first reach the listmodel object that, in this example, can be accessed through grid.children[1] (not sure why it's not at grid.children[0] , but this is just a detail), and finally access the right delegate through grid.children[1].list_model[index] . 要访问委托的数据,必须首先到达listmodel对象,在这个例子中,可以通过grid.children[1]访问(不知道为什么它不在grid.children[0] ,但这只是一个细节),最后通过grid.children[1].list_model[index]访问权限委托。 This can be conveniently packaged inside a getChild() function. 这可以方便地打包在getChild()函数中。

The final recommendation is to give a meaningful objectName to almost everything since the beginning of development. 最后的建议是从开发开始以来为几乎所有内容提供有意义的objectName This practice ease the debugging a lot and, even if it's evil, allows to access data from C++ . 这种做法很容易调试,即使它是邪恶的,也允许从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)
            }
        }
    }
}

For QML type ListView, the following simple function can get the delegate instance at a specific index: 对于QML类型ListView,以下简单函数可以获取特定索引处的委托实例:

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

Following is a QML test example that makes use of the above function, with added error checking and logging code, based on Qt 5.5: 以下是一个QML测试示例,该示例使用上述函数,添加了基于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" }
    }
}

Although the above function works well with these simple "fruitModel" and "fruitView" objects, it may need to be further enhanced when dealing with more complex ListModel and ListView instances. 虽然上述函数适用于这些简单的“fruitModel”和“fruitView”对象,但在处理更复杂的ListModel和ListView实例时可能需要进一步增强。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM