首页 文章

如何从QML中的GridView或ListView获取实例化的委托组件

提问于
浏览
16

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

我有一个GridView,其中包含模型中的许多项目 . GridView的委托创建每个项目的缩略图视图 . 单击时,它会显示项目的详细全屏视图 . 我想要一个很好的转换,它显示缩略图从GridView中的位置扩展出来,当详细视图被解除时,缩小回到GridView中 .

诀窍是,详细视图本身是ListView的委托,因此您可以一次在一个屏幕的详细视图之间进行分页 . 这意味着只需调整GridView的委托项目大小或某些内容的解决方案将无效 . 此外,由于您可以寻呼到ListView中的任何项目,因此必须仅基于模型中可用的信息或模型项目的索引来返回GridView(例如,我无法存储用于启动的模拟项目的鼠标的坐标 . 详细的观点或东西) .

扩展动画相当容易,因为委托项目具有用于知道其自己的位置的单击处理程序的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.onCompletedComponent.onDestruction 保留所有创建的委托项的列表 . 为了有用,它应该是模型项或index =>委托项的映射 . 麻烦的是,basic types docs(特别是variant)似乎表明这种 Map 不可能用纯QML创建 . 所以听起来这意味着将此 Map 创建为C类并在QML中使用委托组件中的 onCompleted / onDestruction 以使其保持最新 . 对于应该简单的事情来说,似乎有点危险和苛刻 .

  • This mailing list post似乎表明Flickable的 contentItem 属性可用于枚举委托项 . 然后我发现this post称这是不好的做法 . 我怀疑这将是一个合法的解决方案 . 看起来太可靠了,无法可靠地工作 .

这就是我到目前为止所做的一切 .

4 回答

  • 0

    经过一些调查后,事实证明 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";
                    }
                }
            }
        }
    }
    

    请告诉我有一种更健壮的方式!

  • 0

    如果有人对如何在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() 相同的对象 . 也许有人发现这很有用,它肯定会在四天前帮助我...... :)

  • 9

    经过短短几个月的QML编程,我已经提出了这个解决方案,类似于OP的解决方案,但无论如何我都会发布它,希望它可以帮助其他人整理这个东西 .

    此示例中基本上有三个组件: GridView 显示可视组件(查看器), 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)
                }
            }
        }
    }
    
  • 2

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

相关问题