【问题标题】:How to get an instantiated delegate component from a GridView or ListView in QML如何从 QML 中的 GridView 或 ListView 获取实例化的委托组件
【发布时间】: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.onCompletedComponent.onDestruction 保存所有创建的委托项目的列表。为了有用,它应该是模型项或索引 => 委托项的映射。麻烦的是,basic types docs(尤其是variant)似乎表明这种地图不可能在纯 QML 中创建。所以听起来这意味着将这个映射创建为一个 C++ 类,并在 QML 中与委托组件中的 onCompleted/onDestruction 一起使用它以使其保持最新。对于本应简单的事情,似乎有点危险和笨拙。

  • This mailing list post 似乎表明 Flickable 的 contentItem 属性可用于枚举委托项目。然后我发现this post 认为这是不好的做法。我仍在研究它,但我怀疑这将是一个合法的解决方案。看起来太老套了,无法可靠地工作。

到目前为止,这就是我所拥有的。

【问题讨论】:

    标签: qt qml


    【解决方案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] 似乎效果很好。
          猜你喜欢
          • 1970-01-01
          • 2017-01-21
          • 2018-01-21
          • 2014-09-29
          • 1970-01-01
          • 2015-12-03
          • 2019-09-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多