如何从QML中的GridView或ListView获取实例化的委托组件
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何从QML中的GridView或ListView获取实例化的委托组件相关的知识,希望对你有一定的参考价值。
我的难题一般来说是:通过GridView之外的一些动作,我想根据之前选择的特定模型项或索引来计算GridView中特定委托项的坐标。
我有一个GridView,其中有许多项目在模型中。 GridView的委托创建每个项目的缩略图视图。单击时,它会显示项目的详细全屏视图。我想要一个很好的转换,它显示缩略图从GridView中的位置扩展出来,当详细视图被解除时,缩小回到GridView中。
诀窍是,详细视图本身就是ListView的委托,因此您可以一次在一个屏幕之间在详细视图之间进行分页。这意味着只需调整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
保留创建的所有委托项的列表。为了有用,它应该是模型项或index =>委托项的映射。麻烦的是,basic types docs(特别是variant)似乎表明这种地图不可能用纯QML创造。所以听起来这意味着将此地图创建为C ++类,并在QML中使用委托组件中的onCompleted
/onDestruction
来使其保持最新。对于应该简单的事情来说,似乎有点危险和苛刻。 - This mailing list post似乎表明Flickable的
contentItem
属性可用于枚举委托项目。然后我发现this post称这是不好的做法。我仍在调查,但我怀疑这将是一个合法的解决方案。看起来太可靠了,无法可靠地工作。
这就是我到目前为止所做的一切。
经过一番调查后发现,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";
}
}
}
}
}
请告诉我有一个更健壮的方式!
如果有人对如何在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();
}
重要的是要注意(以及发布此内容的主要原因),从children()
访问QQuickItem
将调用QObject::children()
方法,该方法不一定返回与QQuickItem::childItems()
相同的对象。也许有人发现这很有用,它肯定会在四天前帮助我... :)
经过短短几个月的QML编程,我已经提出了这个解决方案,类似于OP的解决方案,但无论如何我都会发布它,希望它可以帮助其他人整理这个东西。
在这个例子中基本上有三个组件:一个用于显示视觉组件的GridView
(查看器),一个包含Item
(数据)列表的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 以上是关于如何从QML中的GridView或ListView获取实例化的委托组件的主要内容,如果未能解决你的问题,请参考以下文章