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

时间:2012-01-27 20:17:03

标签: qt qml

我的难题一般来说是:通过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保留所有已创建的委托项的列表。为了有用,它应该是模型项或索引的地图=>代表项目。麻烦的是,basic types docs(尤其是variant)似乎表明这种地图无法用纯QML创建。所以听起来这意味着将此地图创建为C ++类,并在委托组件中使用QML与onCompleted / onDestruction进行比较,以使其保持最新。对于一些应该简单的事情来说,似乎有点危险和苛刻。

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

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

4 个答案:

答案 0 :(得分:9)

经过一番调查后发现,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";
                }
            }
        }
    }
}

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

答案 1 :(得分:2)

如果有人对如何在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();
}

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

答案 2 :(得分:0)

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

此示例中基本上有三个组件:一个GridView用于显示可视组件(查看器),一个Item包含一个QObjects(数据)列表和一个{{1}包含彩色Component(委托)。

查看器获取数据并显示它创建委托。遵循此模式,更改委托内容的最简单方法是访问其数据。要访问代理人的数据,必须先访问Rectangle对象,在此示例中,可以通过listmodel访问该对象(不确定为什么它不在grid.children[1],但这是只是一个细节),最后通过grid.children[0]访问正确的代表。这可以方便地打包在grid.children[1].list_model[index]函数中。

最后的建议是从开发开始就为所有提供有意义的getChild()。这种做法使调试变得更加容易,即使它是邪恶的,也允许从objectName访问数据。

C++

答案 3 :(得分:0)

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