Mon énigme, exprimée de manière générale, est la suivante: grâce à une action en dehors de GridView, je souhaite déterminer les coordonnées d’un élément délégué particulier dans GridView en se basant uniquement sur un élément de modèle ou un index sélectionné auparavant.
J'ai un GridView avec un certain nombre d'éléments dans le modèle. Le délégué de GridView crée une vue miniature de chaque élément. Lorsque vous cliquez dessus, une vue détaillée de l’élément en plein écran apparaît. Je voudrais une transition de Nice qui montre la vignette en train de sortir de sa place dans GridView et, lorsque la vue détaillée est supprimée, de revenir à GridView.
Le truc, c'est que la vue détaillée est elle-même un délégué d'un ListView, ce qui vous permet de feuilleter entre les vues détaillées, écran par écran. Cela signifie une solution qui redimensionne simplement l'élément délégué de GridView ou quelque chose ne fonctionnera pas. De plus, comme vous pouvez rechercher une page dans n'importe quel élément de la liste, le retour dans la grille doit être effectué uniquement en fonction des informations disponibles dans le modèle ou de l'index de celui-ci (par exemple, je ne peux pas stocker les coordonnées de la souris MouseArea utilisée pour lancer la vue détaillée ou quelque chose).
L'animation d'extension est assez facile, car l'élément délégué a un MouseArea pour le gestionnaire de clics qui connaît son propre emplacement, de sorte qu'il peut être transmis à la fonction qui démarre l'animation. C'est l'inverse que je ne peux pas comprendre: à partir de l'élément/index du modèle dans ListView, comment puis-je déterminer les coordonnées de l'élément associé dans GridView?
Je ne trouve rien dans la documentation qui semble vous permettre d'avoir accès à une instance d'élément délégué à partir d'une instance d'élément de modèle ou même d'un index. Le GridView a indexAt()
qui renvoie l'index en fonction des coordonnées. Je pense que je pourrais me débrouiller avec l'inverse, mais cela ne semble pas exister.
Voici un exemple plus concret. Excuses pour la longueur; C'est l'exemple de code le plus court que je pourrais trouver qui décrit avec précision mon problème:
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";
}
}
}
}
}
Des idées? Il est possible que je ne fasse que mal. Si ce que j'essaie de faire spécifiquement est impossible, y a-t-il un autre moyen de structurer cela? Merci!
Edit: Quelques idées que j'ai eues (dont aucune, à mon avis, n'est faisable):
Conservez une liste de tous les éléments de délégué créés à l'aide de Component.onCompleted
et Component.onDestruction
. Pour être utile, il doit s'agir d'une carte d'élément de modèle ou d'un index => d'élément délégué. Le problème est que les types de base docs (en particulier variante ) semblent indiquer que ce type de carte est impossible à créer en QML pur. Il semble donc que cela signifierait créer cette carte en tant que classe C++ et l'utiliser dans QML avec onCompleted
/onDestruction
dans le composant délégué pour la maintenir à jour. Cela semble un peu dangereux et lourd pour quelque chose qui devrait être simple.
Cet article de liste de diffusion semble indiquer que la propriété contentItem
de Flickable peut être utilisée pour énumérer les éléments délégués. Ensuite, j'ai trouvé ce post l'appelant comme une mauvaise pratique. Je suis encore à la recherche, mais je suis sceptique, ce sera une solution légitime. Cela semble trop compliqué pour fonctionner de manière fiable.
C'est tout ce que j'ai jusqu'à présent.
Après quelques recherches, il s'avère que contentItem
tient les délégués instanciés pour un scintillable. Comme je l'ai dit plus haut, je suis sceptique quant au fait qu'il s'agisse bien de la meilleure façon de procéder, voire d'une bonne période, mais cela semble fonctionner. Je posterai le code complet de cette solution hacky ci-dessous, mais j'espère toujours qu'il existe une meilleure solution. Le bit vraiment important est la nouvelle fonction getDelegateInstanceAt()
dans 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'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";
}
}
}
}
}
S'il vous plaît dites-moi qu'il existe un moyen plus robuste!
Juste au cas où quelqu'un voudrait savoir comment accomplir cela en C++, voici un extrait rapide:
//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();
}
Il est important de noter (et la principale raison de la publication de cette information) que l'accès à children()
à partir de QQuickItem
invoquera la méthode QObject::children()
, qui ne renvoie pas nécessairement les mêmes objets que QQuickItem::childItems()
. Il y a quatre jours... :)
Pour le type QML ListView, la fonction simple suivante peut obtenir l'instance de délégué à un index spécifique:
function getDelegateInstanceAt(index) {
return contentItem.children[index];
}
Voici un exemple de test QML qui utilise la fonction ci-dessus, avec contrôle supplémentaire des erreurs et code de journalisation, basé sur 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" }
}
}
Bien que la fonction ci-dessus fonctionne bien avec ces objets simples "fruitModel" et "fruitView", il peut être nécessaire de l'améliorer davantage pour traiter des instances plus complexes de ListModel et de ListView.
Après seulement quelques mois de programmation QML, j'ai proposé cette solution, similaire à celle de l'OP, mais je la posterai quand même dans l'espoir que cela puisse aider d'autres personnes à résoudre ce problème.
Cet exemple comporte essentiellement trois composants: un GridView
pour afficher les composants visuels (visualiseur), un Item
qui contient une liste de QObjects
(données) et un Component
qui entoure un Rectangle
de couleur (délégué).
Le visualiseur prend les données et les montre en créant des délégués. Suivant ce schéma, le moyen le plus simple de modifier le contenu d'un délégué consiste à accéder à ses données. Pour accéder aux données d'un délégué, vous devez d'abord atteindre l'objet listmodel
auquel, dans cet exemple, vous pouvez accéder via grid.children[1]
(vous ne savez pas pourquoi ce n'est pas à grid.children[0]
, mais il ne s'agit que d'un détail), et vous pouvez enfin accéder au bon délégué via grid.children[1].list_model[index]
. Cela peut être facilement emballé dans une fonction getChild()
.
La recommandation finale est de donner une objectName
significative à presque tout depuis le début du développement. Cette pratique facilite beaucoup le débogage et, même si c'est mauvais, permet d'accéder aux données depuis 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)
}
}
}
}