如何从 QML 中的 GridView 或 ListView 获取实例化的委托组件
Posted
技术标签:
【中文标题】如何从 QML 中的 GridView 或 ListView 获取实例化的委托组件【英文标题】:How to get an instantiated delegate component from a GridView or ListView in QML 【发布时间】:2012-02-20 19:24:03 【问题描述】:一般来说,我的难题是:通过 GridView 之外的一些操作,我想仅根据之前选择的特定模型项或索引来计算 GridView 中特定委托项的坐标。
我有一个 GridView,其中包含模型中的许多项目。 GridView 的委托创建每个项目的缩略图视图。单击时,它会显示该项目的详细全屏视图。我想要一个很好的过渡,显示缩略图从它在 GridView 中的位置展开,当详细视图被关闭时,缩回到 GridView 中。
诀窍在于,详细视图本身是 ListView 的委托,因此您可以一次在一个屏幕的详细视图之间进行分页。这意味着一个简单地调整 GridView 的委托项目大小的解决方案,否则将无法正常工作。此外,由于您可以分页到 ListView 中的任何项目,因此返回到 GridView 必须仅基于模型中可用的信息或模型项目的索引(例如,我无法存储用于启动的 MouseArea 的坐标详细视图之类的)。
展开动画相当简单,因为代理项有一个用于单击处理程序的 MouseArea,它知道自己的位置,因此可以将其传递给启动动画的函数。反过来我想不通:从ListView中的model item/index,如何找出GridView中相关item的坐标?
我在文档中找不到任何似乎可以让您从模型项实例甚至索引访问委托项实例的内容。 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
保存所有创建的委托项目的列表。为了有用,它应该是模型项或索引 => 委托项的映射。麻烦的是,basic types docs(尤其是variant)似乎表明这种地图不可能在纯 QML 中创建。所以听起来这意味着将这个映射创建为一个 C++ 类,并在 QML 中与委托组件中的 onCompleted
/onDestruction
一起使用它以使其保持最新。对于本应简单的事情,似乎有点危险和笨拙。
This mailing list post 似乎表明 Flickable 的 contentItem
属性可用于枚举委托项目。然后我发现this post 认为这是不好的做法。我仍在研究它,但我怀疑这将是一个合法的解决方案。看起来太老套了,无法可靠地工作。
到目前为止,这就是我所拥有的。
【问题讨论】:
【参考方案1】:对于 QML 类型的 ListView,下面的简单函数可以获取特定索引处的委托实例:
function getDelegateInstanceAt(index)
return contentItem.children[index];
以下是一个使用上述功能的 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"
虽然上述函数可以很好地处理这些简单的“fruitModel”和“fruitView”对象,但在处理更复杂的 ListModel 和 ListView 实例时可能需要进一步增强。
【讨论】:
【参考方案2】:经过短短几个月的 QML 编程,我想出了这个解决方案,类似于 OP 的解决方案,但无论如何我都会发布它,希望它可以帮助其他人解决这个问题。
此示例中基本上包含三个组件:GridView
用于显示可视组件(查看器),Item
包含 QObjects
(数据)列表和 Component
,其中包含有颜色的 @987654325 @(代表)。
查看器获取数据并显示它创建委托。按照这种模式,更改委托内容的最简单方法是访问其数据。要访问委托的数据,必须首先到达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)
【讨论】:
【参考方案3】:以防万一有人对如何在 C++ 中实现这一点感兴趣,这里有一个快速的 sn-p:
//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】:经过一番调查,事实证明contentItem
确实拥有 Flickable 的实例化代表。正如我上面所说,我怀疑这真的是做到这一点的最佳方式,甚至是一个好的方式时期,但它似乎确实有效。我将在下面发布这个 hacky 解决方案的完整代码,但我仍然希望有更好的方法。真正重要的是 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";
请告诉我还有更健壮的方法!
【讨论】:
只是想知道您是否遇到过更好的解决方案或更好的实现来从另一个 QML 文件访问模型的委托,或者您是否仍在使用上述方法? 不,据我所知,这是“官方”的做法。 似乎“黑魔法”试图克服的问题之一是子列表被 GridView 的兄弟姐妹污染。我认为您可以将 GridView 包装在一个 Rectangle 中,从而简化您的子列表。然后简单的 summaryView.contentItem.children[i] 似乎效果很好。以上是关于如何从 QML 中的 GridView 或 ListView 获取实例化的委托组件的主要内容,如果未能解决你的问题,请参考以下文章
PySide2/QML 填充和动画 Gridview 模型/委托
在 QML 中单击时如何在 GridView 中显示较大版本的选定图像