2012-01-27 13 views
16

Il mio enigma, formulato in generale, è: attraverso alcune azioni all'esterno di GridView, voglio calcolare le coordinate di un particolare elemento delegato in GridView basato solo su un particolare articolo di modello o indice selezionato prima.Come ottenere un componente delegato istanziato da un GridView o ListView in QML

Ho un GridView con un numero di elementi nel modello. Il delegato di GridView crea una vista in miniatura di ciascun elemento. Quando si fa clic, viene visualizzata una vista dettagliata a schermo intero dell'oggetto. Mi piacerebbe una transizione gradevole che mostri la miniatura che si espande dalla sua posizione in GridView e quando la vista dettagliata viene eliminata, ridiscendendo verso il basso in GridView.

Il trucco è che la vista dettagliata è a sua volta un delegato di una ListView in modo da poter scorrere tra le viste dettagliate una schermata alla volta. Ciò significa una soluzione che semplicemente ridimensiona l'elemento delegato di GridView o qualcosa non funzionerà. Inoltre, poiché è possibile eseguire una pagina su qualsiasi elemento in ListView, è necessario tornare a GridView basandosi solo sulle informazioni disponibili nel modello o sull'indice dell'oggetto del modello (ad esempio, non è possibile memorizzare le coordinate di MouseArea utilizzate per avviare il vista dettagliata o qualcosa del genere).

L'animazione di espansione è abbastanza semplice, poiché l'elemento delegato ha una MouseArea per il gestore di clic che conosce il proprio posizionamento, in modo che possa essere trasmesso alla funzione che avvia l'animazione. È il contrario che non riesco a capire: dall'elemento modello/indice in ListView, come faccio a capire le coordinate dell'elemento correlato in GridView?

Non riesco a trovare nulla nei documenti che sembra consentire di avere accesso a un'istanza di articolo delegato da un'istanza di elemento modello o anche un indice. GridView ha indexAt() che restituisce l'indice in base alle coordinate. Penso che potrei arrangiarmi con il contrario, ma non sembra esistere.

Ecco un esempio più concreto. Ci scusiamo per la lunghezza; questo è il codice di esempio più breve che potrei inventare che descriva accuratamente il mio problema:

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"; 
       } 
      } 
     } 
    } 
} 

Qualche idea? È possibile che io stia solo andando nel modo sbagliato. Se ciò che sto cercando di fare nello specifico è impossibile, c'è un altro modo in cui dovrei essere architettato? Grazie!


Edit: Alcune idee che ho avuto (nessuno dei quali credo siano fattibili):

  • tenere un elenco di tutti gli elementi delegato creati, utilizzando Component.onCompleted e Component.onDestruction. Per essere utile, dovrebbe essere una mappa di elemento del modello o indice => elemento delegato. Il problema è che il basic types docs (in particolare variant) sembra indicare che questo tipo di mappa è impossibile da creare in puro QML. Sembra quindi che ciò significherebbe creare questa mappa come classe C++ e utilizzarla in QML con onCompleted/onDestruction all'interno del componente delegato per mantenerlo aggiornato. Sembra un po 'pericoloso e pesante per qualcosa che dovrebbe essere semplice.

  • This mailing list post sembra indicare che la proprietà contentItem di Flickable può essere utilizzata per enumerare gli elementi delegati. Poi ho trovato this post definendolo una cattiva pratica. Ci sto ancora guardando, ma sono scettico sul fatto che questa sarà una soluzione legittima. Sembra troppo hacky per funzionare in modo affidabile.

Questo è tutto quello che ho ottenuto finora.

risposta

9

Dopo alcune indagini, risulta che contentItem detiene i delegati istanziati per un Flickable. Come ho detto sopra, sono scettico sul fatto che questo sia davvero il modo migliore per farlo, o anche un buon periodo, ma sembra funzionare. Di seguito pubblicherò il codice completo per questa soluzione hacky, ma spero ancora che esista un modo migliore. Il bit davvero importante è la nuova funzione getDelegateInstanceAt() in 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"; 
       } 
      } 
     } 
    } 
} 

Per favore dimmi che c'è un modo più solido!

+0

Ti chiedi solo se hai trovato soluzioni migliori o implementazioni migliori per accedere ai delegati di un modello da un altro file QML o se stai ancora utilizzando l'approccio sopra? – stackunderflow

+0

No, per quanto posso dire questo è il modo "ufficiale" per farlo. – chazomaticus

+0

Sembra uno dei problemi che la "magia nera" cerca di superare è la lista dei bambini che viene inquinata dai fratelli di GridView. Penso che puoi avvolgere il tuo GridView in un rettangolo, semplificando la tua lista dei bambini. Quindi semplicemente summaryView.contentItem.children [i] sembra funzionare bene. – SketchBookGames

2

Solo nel caso qualcuno è interessato in come per raggiungere questo obiettivo in C++, ecco un breve frammento:

//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(); 
} 

E 'importante notare (e la ragione principale per la pubblicazione di questo), che l'accesso children() da QQuickItem invocherà il metodo QObject::children(), che non restituisce necessariamente gli stessi oggetti di QQuickItem::childItems(). Forse qualcuno lo trova utile, sicuramente mi aiuterebbe quattro giorni fa ... :)

0

Dopo pochi mesi di programmazione QML ho trovato questa soluzione, simile a quella dell'OP, ma Lo posterò comunque nella speranza che possa aiutare altre persone a risolvere questa cosa.

Ci sono fondamentalmente tre componenti in questo esempio: un GridView per mostrare componenti visivi (visualizzatore), un Item che contiene un elenco di QObjects (dati) e un Component che racchiudono una colorata Rectangle (delegato).

Il visualizzatore acquisisce i dati e li mostra creando delegati. Seguendo questo schema, il modo più semplice per modificare il contenuto di un delegato è quello di accedere ai suoi dati. Per accedere ai dati di un delegato, è necessario prima raggiungere l'oggetto listmodel che, in questo esempio, è possibile accedere tramite grid.children[1] (non è sicuro perché non è a grid.children[0], ma questo è solo un dettaglio) e infine accedere al delegato di destra tramite grid.children[1].list_model[index]. Questo può essere comodamente imballato all'interno di una funzione getChild().

La raccomandazione finale è di dare un significativo objectName a quasi tutto dall'inizio dello sviluppo. Questa pratica facilita molto il debugging e, anche se è malvagio, consente di accedere ai dati da 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) 
      } 
     } 
    } 
} 
0

Per QML tipo ListView, la seguente funzione semplice può ottenere l'istanza delegato ad un indice specifico:

function getDelegateInstanceAt(index) { 
    return contentItem.children[index]; 
} 

seguito fornisce un esempio di test QML che fa uso della funzione di cui sopra, con l'errore aggiunto controllo e registrazione del codice, basato su 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" } 
    } 
} 

Sebbene la funzione di cui sopra funziona bene con questi semplici "fruitModel" e oggetti "fruitView", può avere bisogno di essere ulteriormente e potenziato quando si tratta di istanze ListModel e ListView più complesse.

Problemi correlati