如何从 QML 中的 GridView 或列表视图获取实例化的委托组件



我的难题,一般来说,是:通过 GridView 之外的一些操作,我想仅根据之前选择的特定模型项或索引来确定 GridView 中特定委托项的坐标。

我有一个 GridView,其中包含模型中的许多项目。 GridView 的委托创建每个项的缩略图视图。 单击时,它会显示项目的详细全屏视图。 我想要一个很好的过渡,显示缩略图从其在 GridView 中的位置展开,当详细视图被关闭时,缩小到原位的 GridView。

诀窍是,详细视图本身是 ListView 的委托,因此您可以一次在一个屏幕之间分页详细视图。 这意味着仅调整 GridView 委托项大小或其他内容的解决方案将不起作用。 此外,由于您可以分页到 ListView 中的任何项,因此必须仅根据模型或模型项索引中的可用信息(例如,我无法存储用于启动详细视图或其他内容的鼠标区域的坐标(来返回 GridView。

扩展动画相当容易,因为委托项有一个 MouseArea 用于知道其自身位置的单击处理程序,因此可以将其传递给启动动画的函数。 我无法弄清楚的相反情况:从列表视图中的模型项/索引中,如何计算出 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 保留创建的所有委托项的列表。 为了有用,它应该是模型项或索引 => 委托项的映射。 麻烦的是,基本类型的文档(尤其是变体(似乎表明这种映射不可能在纯QML中创建。 所以听起来这意味着将此映射创建为C++类,并在 QML 中将其与委托组件中的 onCompleted/onDestruction一起使用以使其保持最新状态。 对于应该简单的事情来说,似乎有点危险和严厉。

  • 这个邮件列表帖子似乎表明Flickable的contentItem属性可用于枚举委托项目。 然后我发现这篇文章称这是不好的做法。 我仍在研究它,但我怀疑这将是一个合法的解决方案。 似乎太笨拙了,无法可靠地工作。

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

经过一些调查,事实证明contentItem确实持有 Flickable 的实例化委托。 正如我上面所说,我怀疑这是否真的是最好的方法,甚至是一个好方法,但它似乎确实有效。 我将在下面发布这个黑客解决方案的完整代码,但我仍然希望有更好的方法。 真正重要的一点是 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();
}

重要的是要注意(以及发布此内容的主要原因(,从QQuickItem访问children()将调用QObject::children()方法,该方法不一定返回与QQuickItem::childItems()相同的对象。也许有人觉得这很有用,四天前肯定会对我有帮助...... :)

经过几个月的QML编程,我想出了这个解决方案,类似于OP的解决方案,但我还是会发布它,希望它可以帮助其他人解决这个问题。

此示例中基本上有三个组件:用于显示可视组件的GridView(查看器(、包含QObjects列表(数据(的Item和包含彩色Rectangle(委托(的Component

查看器获取数据并显示它创建委托。遵循此模式,更改委托内容的最简单方法是访问其数据。要访问委托的数据,必须首先到达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)
            }
        }
    }
}
<</div> div class="one_answers">

对于 QML 类型 ListView,以下简单函数可以在特定索引处获取委托实例:

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

以下是基于 Qt 5.5 的 QML 测试示例,它使用了上述函数,并添加了错误检查和日志记录代码:

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实例时,可能需要进一步增强。

最新更新