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具有
这是一个更具体的例子。抱歉的长度;这是我能想到的最短的示例代码,准确地描述了我的问题:
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.onCompleted 和Component.onDestruction 创建的所有委托项的列表。为了有用,它应该是模型项或索引=>委托项的映射。麻烦的是,基本类型docs(尤其是变体)似乎表明无法在纯QML中创建这种映射。因此,听起来这意味着将这个映射创建为C ++类,并在QML中将其与委托组件中的onCompleted /onDestruction 结合使用以使其保持最新。对于一些简单的事情来说似乎有点危险和笨拙。 -
该邮件列表帖子似乎表明Flickable的
contentItem 属性可用于枚举委托项目。然后我发现这篇文章称这是不好的做法。我仍在调查,但我怀疑这将是一个合法的解决方案。似乎太笨拙而无法可靠地工作。
到目前为止,这就是我所拥有的。
经过一番调查,结果发现
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(); } |
重要的是要注意(也是发布此消息的主要原因),从
也许有人觉得这很有用,它肯定会在四天前对我有帮助... :)
对于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中的解决方案类似,但无论如何我都会发布它,以希望它可以帮助其他人解决此问题。
在此示例中,基本上有三个组件:一个用于显示视觉组件(查看器)的
查看器获取数据并显示以创建委托。按照这种模式,更改委托内容的最简单方法是访问其数据。要访问委托的数据,必须首先到达
最终建议是对自开发开始以来的几乎所有内容都赋予有意义的
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) } } } } |