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

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

通常,我的难题是:通过GridView之外的一些操作,我想仅基于之前选择的特定模型项或索引来计算GridView中特定委托项的坐标。

我在模型中有一个带有多个项目的GridView。 GridView的委托创建每个项目的缩略图视图。单击后,将显示该项目的详细全屏视图。我想要一个很好的过渡,显示缩略图从其在GridView中的位置扩展出来,并且当关闭详细视图时,将其缩小回原位。

诀窍是,详细视图本身就是ListView的委托,因此您可以一次在一个屏幕上在详细视图之间进行分页。这意味着仅调整GridView委托项目大小的解决方案,否则将无法正常工作。另外,由于您可以分页到ListView中的任何项目,因此仅根据模型中可用的信息或模型项目的索引即可返回GridView(例如,我无法存储用于启动鼠标的MouseArea的坐标)详细视图或其他内容)。

扩展动画非常容易,因为委托项具有单击处理程序的MouseArea,它知道自己的位置,因此可以传递给启动动画的函数。相反,我无法弄清楚:从ListView的模型项/索引中,如何计算GridView中相关项的坐标?

我在文档中找不到任何似乎可以让您从模型项目实例甚至索引访问委托项目实例的内容。 GridView具有indexAt(),可根据坐标返回索引。我想我可以解决相反的问题,但它似乎不存在。

这是一个更具体的例子。抱歉的长度;这是我能想到的最短的示例代码,准确地描述了我的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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.onCompletedComponent.onDestruction创建的所有委托项的列表。为了有用,它应该是模型项或索引=>委托项的映射。麻烦的是,基本类型docs(尤其是变体)似乎表明无法在纯QML中创建这种映射。因此,听起来这意味着将这个映射创建为C ++类,并在QML中将其与委托组件中的onCompleted / onDestruction结合使用以使其保持最新。对于一些简单的事情来说似乎有点危险和笨拙。

  • 该邮件列表帖子似乎表明Flickable的contentItem属性可用于枚举委托项目。然后我发现这篇文章称这是不好的做法。我仍在调查,但我怀疑这将是一个合法的解决方案。似乎太笨拙而无法可靠地工作。

到目前为止,这就是我所拥有的。


经过一番调查,结果发现contentItem确实保存了Flickable的实例化委托。就像我在上面说的那样,我对此确实是最好的方法甚至是一个好的方法期表示怀疑,但是它确实起作用了。我将在下面发布此hacky解决方案的完整代码,但我仍然希望有更好的方法。真正重要的一点是GridView中新的getDelegateInstanceAt()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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";
                }
            }
        }
    }
}

请告诉我,还有一种更强大的方法!


以防万一有人对如何用C ++做到这一点感兴趣,下面是一个简短的片段:

1
2
3
4
5
6
7
//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()相同的对象。
也许有人觉得这很有用,它肯定会在四天前对我有帮助... :)


对于QML类型的ListView,以下简单函数可以在特定索引处获取委托实例:

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

以下是一个基于Qt 5.5的QML测试示例,该示例使用上述功能,并添加了错误检查和日志记录代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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实例时,可能需要对其进行进一步增强。


经过仅几个月的QML编程,我就提出了这种解决方案,与OP中的解决方案类似,但无论如何我都会发布它,以希望它可以帮助其他人解决此问题。

在此示例中,基本上有三个组件:一个用于显示视觉组件(查看器)的GridView,一个包含一个QObjects(数据)列表的Item和一个包含彩色RectangleComponent(委托) )。

查看器获取数据并显示以创建委托。按照这种模式,更改委托内容的最简单方法是访问其数据。要访问委托的数据,必须首先到达listmodel对象,在此示例中,该对象可以通过grid.children[1]访问(不确定为什么它不在grid.children[0]处,但这只是一个细节),并且最后通过grid.children[1].list_model[index]访问正确的委托。可以方便地将其包装在getChild()函数中。

最终建议是对自开发开始以来的几乎所有内容都赋予有意义的objectName。这种做法极大地简化了调试,即使这样做很邪恶,也允许从C++访问数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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)
            }
        }
    }
}