Как получить экземпляр делегированного компонента из GridView или ListView в QML
Моя головоломка, как правило, заключается в следующем: через какое-то действие вне GridView я хочу определить координаты конкретного элемента делегата в GridView, основываясь только на определенном элементе модели или индексе, выбранном ранее.
У меня есть GridView с рядом элементов в модели. Делегат GridView создает эскиз каждого элемента. При нажатии на нее открывается подробный полноэкранный вид элемента. Мне нужен приятный переход, который показывает, что миниатюра расширяется со своего места в GridView, и когда подробное представление отклоняется, сжимается обратно в GridView на месте.
Фокус в том, что подробный вид сам по себе является делегатом ListView, поэтому вы можете размещать страницы между подробными просмотрами по одному экрану за раз. Это означает, что решение, которое просто изменяет размер элемента делегирования GridView или что-то не будет работать. Кроме того, поскольку вы можете ссылаться на любой элемент в ListView, возврат к GridView должен выполняться только на основе информации, доступной в модели или индексе элемента модели (например, я не могу хранить координаты MouseArea, используемые для запуска подробный обзор или что-то еще).
Анимация расширения довольно проста, так как элемент делегата имеет 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
. Чтобы быть полезным, это должна быть карта элемента модели или индекса = > делегировать элемент. Проблема заключается в том, что основные типы документов (особенно variant) показывают, что этот вид карты невозможно создать в чистом QML. Таким образом, похоже, что это будет означать создание этой карты как класса С++ и использование этого в QML с onCompleted
/onDestruction
в компоненте делегата, чтобы поддерживать его в актуальном состоянии. Кажется немного опасным и тяжелым для чего-то, что должно быть просто.
-
Этот список рассылки, кажется, указывает, что свойство Flickable contentItem
может использоваться для перечисления элементов делегата. Затем я нашел этот пост, назвав это неправильной практикой. Я все еще смотрю в нее, но я скептически отношусь к этому, это будет законное решение. Кажется, слишком тяжело работать надежно.
Это все, что у меня есть.
Ответы
Ответ 1
После некоторого расследования выяснится, что contentItem
содержит экземпляр делегатов для Flickable. Как я уже сказал выше, я скептически отношусь к тому, что это действительно лучший способ сделать это или даже хороший период пути, но он, похоже, работает. Я выложу полный код для этого взломанного решения ниже, но я все еще надеюсь на лучший способ. Очень важным битом является новая функция getDelegateInstanceAt()
в GridView.
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 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";
}
}
}
}
}
Скажите, пожалуйста, более надежный способ!
Ответ 2
На всякий случай кто-то заинтересован в том, как это сделать в С++, вот быстрый фрагмент:
//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()
.
Может быть, кто-то найдет это полезным, это наверняка поможет мне четыре дня назад...:)
Ответ 3
После всего лишь нескольких месяцев программирования QML я придумал это решение, похожее на одно из OP, но я все равно отправлю его в надежде, что это поможет другим людям разобраться в этом.
В этом примере есть три компонента: a GridView
для отображения визуальных компонентов (viewer), 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 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)
}
}
}
}
Ответ 4
Для типа ListView класса QML следующая простая функция может получить экземпляр делегата с определенным индексом:
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.