Qt6 QML Book/模型视图/委托

Delegate

委托

When it comes to using models and views in a custom user interface, the delegate plays a huge role in creating a look and behaviour. As each item in a model is visualized through a delegate, what is actually visible to the user are the delegates.

在自定义用户界面中,使用模型和视图时,委托在创建外观和行为方面扮演着重要角色。由于模型中的每个项目都是通过委托可视化的,因此用户实际上看到的是委托。

Each delegate gets access to a number of attached properties, some from the data model, others from the view. From the model, the properties convey the data for each item to the delegate. From the view, the properties convey state information related to the delegate within the view. Let's dive into the properties from the view.

每个委托都可以访问许多附加的属性,有些来自数据模型,有些来自视图。从模型看,属性将每个项的数据传递给委托项。从视图看,属性传递与视图中的委托相关的状态信息。让我们从视图中深入了解属性。

The most commonly used properties attached from the view are ListView.isCurrentItem and ListView.view. The first is a boolean indicating if the item is the current item, while the latter is a read-only reference to the actual view. Through access to the view, it is possible to create general, reusable delegates that adapt to the size and nature of the view in which they are contained. In the example below, the width of each delegate is bound to the width of the view, while the background color of each delegate depends on the attached ListView.isCurrentItem property.

视图提供的最常用附加属性是ListView.isCurrentItemListView.view。前者是一个布尔值,指示该项是否为当前项,而后者是对实际视图的只读引用。通过对视图的访问,可以创建通用的、可重用的委托,以适应包含委托的视图的大小和性质。在下面的示例中,每个委托项的宽度width都绑定到视图的宽度width,而每个委托项的背景色color取决于附加属性ListView.isCurrentItem

import QtQuick

Rectangle {
    width: 120
    height: 300

    gradient: Gradient {
        GradientStop { position: 0.0; color: "#f6f6f6" }
        GradientStop { position: 1.0; color: "#d7d7d7" }
    }

    ListView {
        anchors.fill: parent
        anchors.margins: 20

        focus: true

        model: 100
        delegate: numberDelegate

        spacing: 5
        clip: true
    }

    Component {
        id: numberDelegate

        Rectangle {
            id: wrapper

            required property int index

            width: ListView.view.width
            height: 40

            color: ListView.isCurrentItem ? "#157efb" : "#53d769"
            border.color: Qt.lighter(color, 1.1)

            Text {
                anchors.centerIn: parent

                font.pixelSize: 10

                text: wrapper.index
            }
        }
    }
}

If each item in the model is associated with an action, for instance, clicking an item acts upon it, that functionality is a part of each delegate. This divides the event management between the view, which handles the navigation between items in the view, and the delegate which handles actions on a specific item.

如果模型中的每个项都与某个行为关联,例如,单击某个项对其进行行为操作,则该功能是每个委托的一部分。这将事件管理分为视图(处理视图中项目之间的导航)和委托(处理特定项目上的行为操作)。

The most basic way to do this is to create a MouseArea within each delegate and act on the onClicked signal. This is demonstrated in the example in the next section of this chapter.

最基本的方法是在每个委托中创建一个MouseArea,并对onClicked信号进行响应操作。这将在本章下一节的示例中演示。

Animating Added and Removed Items
为添加和删除的项目设置动画

In some cases, the contents shown in a view changes over time. Items are added and removed as the underlying data model is altered. In these cases, it is often a good idea to employ visual cues to give the user a sense of direction and to help the user understand what data is added or removed.

在某些情况下,视图中显示的内容会随时间而变化。在更改基础数据模型时添加和删除项。在这些情况下,使用视觉提示给用户操作感,并帮助用户理解添加或删除的数据,通常是一个不错的主意。

Conveniently enough, QML views attach two signals, onAdd and onRemove, to each item delegate. By triggering animations from these, it is easy to create the movement necessary to aid the user in identifying what is taking place.

很方便,QML视图将两个信号onAdd和onRemove附加到每个委托项。通过触发这些,可以轻松创建必要的动画,以帮助用户识别正在发生的事情。

The example below demonstrates this through the use of a dynamically populated ListModel. At the bottom of the screen, a button for adding new items is shown. When it is clicked, a new item is added to the model using the append method. This triggers the creation of a new delegate in the view, and the emission of the GridView.onAdd signal. The SequentialAnimation called addAnimation is started from the signal causes the item to zoom into view by animating the scale property of the delegate.

下面的示例通过使用动态填充的ListModel演示了这一点。屏幕底部显示一个用于添加新项目的按钮。单击时,将使用append方法向模型中添加一个新项。这将触发在视图中创建新委托,并发出GridView.onAdd信号。id为addAnimation的串行动画(SequentialAnimation)从信号开始,通过设置委托的缩放属性的动画,使项目缩放到视图中。

GridView.onAdd: addAnimation.start()

SequentialAnimation {
    id: addAnimation
    NumberAnimation { 
        target: wrapper
        property: "scale"
        from: 0
        to: 1
        duration: 250
        easing.type: Easing.InOutQuad 
    }
}

When a delegate in the view is clicked, the item is removed from the model through a call to the remove method. This causes the GridView.onRemove signal to be emitted, starting the removeAnimation SequentialAnimation. This time, however, the destruction of the delegate must be delayed until the animation has completed. To do this, PropertyAction element is used to set the GridView.delayRemove property to true before the animation, and false after. This ensures that the animation is allowed to complete before the delegate item is removed.

单击视图中的委托时,通过调用remove方法将该项从模型中移除。这将导致GridView.onRemove发出信号,启动removeAnimation串行动画(SequentialAnimation)。但是,这一次,代理的销毁必须延迟到动画完成。为此,PropertyAction元素在动画完成前,将GridView.delayRemove属性设置为true,在动画结束后设置为false。这样可以确保在删除委托项前,保证动画完成。

GridView.onRemove: removeAnimation.start()

SequentialAnimation {
    id: removeAnimation
    
    PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: true }
    NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 250; easing.type: Easing.InOutQuad }
    PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: false }
}

Here is the complete code:

以下是完整的代码:

import QtQuick

Rectangle {
    width: 480
    height: 300

    gradient: Gradient {
        GradientStop { position: 0.0; color: "#dbddde" }
        GradientStop { position: 1.0; color: "#5fc9f8" }
    }

    ListModel {
        id: theModel

        ListElement { number: 0 }
        ListElement { number: 1 }
        ListElement { number: 2 }
        ListElement { number: 3 }
        ListElement { number: 4 }
        ListElement { number: 5 }
        ListElement { number: 6 }
        ListElement { number: 7 }
        ListElement { number: 8 }
        ListElement { number: 9 }
    }

    Rectangle {
        property int count: 9

        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.margins: 20

        height: 40

        color: "#53d769"
        border.color: Qt.lighter(color, 1.1)

        Text {
            anchors.centerIn: parent

            text: "Add item!"
        }

        MouseArea {
            anchors.fill: parent

            onClicked: {
                theModel.append({"number": ++parent.count})
            }
        }
    }

    GridView {
        anchors.fill: parent
        anchors.margins: 20
        anchors.bottomMargin: 80

        clip: true

        model: theModel

        cellWidth: 45
        cellHeight: 45

        delegate: numberDelegate
    }

    Component {
        id: numberDelegate

        Rectangle {
            id: wrapper

            required property int index 
            required property int number

            width: 40
            height: 40

            gradient: Gradient {
                GradientStop { position: 0.0; color: "#f8306a" }
                GradientStop { position: 1.0; color: "#fb5b40" }
            }

            Text {
                anchors.centerIn: parent

                font.pixelSize: 10

                text: wrapper.number
            }

            MouseArea {
                anchors.fill: parent

                onClicked: {
                    if (wrapper.index == -1) {
                        return
                    }
                    theModel.remove(wrapper.index)
                }
            }
            
            GridView.onRemove: removeAnimation.start()
            
            SequentialAnimation {
                id: removeAnimation
                
                PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: true }
                NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 250; easing.type: Easing.InOutQuad }
                PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: false }
            }

            GridView.onAdd: addAnimation.start()
            
            SequentialAnimation {
                id: addAnimation
                NumberAnimation { 
                    target: wrapper
                    property: "scale"
                    from: 0
                    to: 1
                    duration: 250
                    easing.type: Easing.InOutQuad 
                }
            }
        }
    }
}

Shape-Shifting Delegates

可形变的委托

A commonly used mechanism in lists is that the current item is expanded when activated. This can be used to dynamically let the item expand to fill the screen to enter a new part of the user interface, or it can be used to provide slightly more information for the current item in a given list.

列表中常用的一种机制是,当前项在激活时展开。这可以用于动态地让项目展开以填充屏幕,以进入用户界面的新部分,也可以用于为给定列表中的当前项目提供较多的信息显示。

In the example below, each item is expanded to the full extent of the ListView containing it when clicked. The extra space is then used to add more information. The mechanism used to control this is a state expanded that each item delegate can enter, where the item is expanded. In that state, a number of properties are altered.

在下面的示例中,单击时,每个项目都会扩展到包含它的ListView的全部范围。然后,额外的空间用于添加更多信息。用于控制种机制是每个委托项都可以进入的expanded状态,expanded状态时,其中项被展开。在这种状态下,许多属性都会发生变化。

First of all, the height of the wrapper is set to the height of the ListView. The thumbnail image is then enlarged and moved down to make it move from its small position into its larger position. In addition to this, the two hidden items, the factsView and closeButton are shown by altering the opacity of the elements. Finally, the ListView is setup.

首先,wrapper的高度height设置为ListView的高度。然后放大并向下移动缩略图图像,使其从小位置移动到大位置。除此之外,两个隐藏项factsView和closeButton通过改变元素的不透明度opacity来显示。最后,设置ListView。

Setting up the ListView involves setting the contentsY, that is the top of the visible part of the view, to the y value of the delegate. The other change is to set interactive of the view to false. This prevents the view from moving. The user can no longer scroll through the list or change the current item.

设置ListView涉及将contentsY(视图可见部分的顶部)设置为委托项的y值。另一个更改是将视图的交互interactive设置为false。这会阻止视图移动。用户无法再滚动列表或更改当前项目。

As the item first is clicked, it enters the expanded state, causing the item delegate to fill the ListView and the contents to rearrange. When the close button is clicked, the state is cleared, causing the delegate to return to its previous state and re-enabling the ListView.

第一次单击项目时,它进入expanded状态,导致项目委托填充ListView并重新排列内容。单击“关闭”按钮时,状态将被清除,从而使委托项返回到其以前的状态并重新启用ListView。

import QtQuick

Item {
    width: 300
    height: 480

    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            GradientStop { position: 0.0; color: "#4a4a4a" }
            GradientStop { position: 1.0; color: "#2b2b2b" }
        }
    }

    ListView {
        id: listView

        anchors.fill: parent

        delegate: detailsDelegate
        model: planets
    }

    ListModel {
        id: planets

        ListElement { name: "Mercury"; imageSource: "images/mercury.jpeg"; facts: "Mercury is the smallest planet in the Solar System. It is the closest planet to the sun. It makes one trip around the Sun once every 87.969 days." }
        ListElement { name: "Venus"; imageSource: "images/venus.jpeg"; facts: "Venus is the second planet from the Sun. It is a terrestrial planet because it has a solid, rocky surface. The other terrestrial planets are Mercury, Earth and Mars. Astronomers have known Venus for thousands of years." }
        ListElement { name: "Earth"; imageSource: "images/earth.jpeg"; facts: "The Earth is the third planet from the Sun. It is one of the four terrestrial planets in our Solar System. This means most of its mass is solid. The other three are Mercury, Venus and Mars. The Earth is also called the Blue Planet, 'Planet Earth', and 'Terra'." }
        ListElement { name: "Mars"; imageSource: "images/mars.jpeg"; facts: "Mars is the fourth planet from the Sun in the Solar System. Mars is dry, rocky and cold. It is home to the largest volcano in the Solar System. Mars is named after the mythological Roman god of war because it is a red planet, which signifies the colour of blood." }
    }

    Component {
        id: detailsDelegate

        Item {
            id: wrapper

            required property string name
            required property string imageSource
            required property string facts

            width: listView.width
            height: 30

            Rectangle {
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: parent.top

                height: 30

                color: "#333"
                border.color: Qt.lighter(color, 1.2)
                Text {
                    anchors.left: parent.left
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.leftMargin: 4

                    font.pixelSize: parent.height-4
                    color: '#fff'

                    text: wrapper.name
                }
            }


            Rectangle {
                id: image

                width: 26
                height: 26

                anchors.right: parent.right
                anchors.top: parent.top
                anchors.rightMargin: 2
                anchors.topMargin: 2

                color: "black"


                Image {
                    anchors.fill: parent

                    fillMode: Image.PreserveAspectFit

                    source: wrapper.imageSource
                }
            }

            MouseArea {
                anchors.fill: parent
                onClicked: parent.state = "expanded"
            }

            Item {
                id: factsView

                anchors.top: image.bottom
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.bottom: parent.bottom

                opacity: 0

                Rectangle {
                    anchors.fill: parent

                    gradient: Gradient {
                        GradientStop { position: 0.0; color: "#fed958" }
                        GradientStop { position: 1.0; color: "#fecc2f" }
                    }
                    border.color: '#000000'
                    border.width: 2

                    Text {
                        anchors.fill: parent
                        anchors.margins: 5

                        clip: true
                        wrapMode: Text.WordWrap
                        color: '#1f1f21'

                        font.pixelSize: 12

                        text: wrapper.facts
                    }
                }
            }

            Rectangle {
                id: closeButton

                anchors.right: parent.right
                anchors.top: parent.top
                anchors.rightMargin: 2
                anchors.topMargin: 2

                width: 26
                height: 26

                color: "#157efb"
                border.color: Qt.lighter(color, 1.1)

                opacity: 0

                MouseArea {
                    anchors.fill: parent
                    onClicked: wrapper.state = ""
                }
            }

            states: [
                State {
                    name: "expanded"

                    PropertyChanges { target: wrapper; height: listView.height }
                    PropertyChanges { target: image; width: listView.width; height: listView.width; anchors.rightMargin: 0; anchors.topMargin: 30 }
                    PropertyChanges { target: factsView; opacity: 1 }
                    PropertyChanges { target: closeButton; opacity: 1 }
                    PropertyChanges { target: wrapper.ListView.view; contentY: wrapper.y; interactive: false }
                }
            ]

            transitions: [
                Transition {
                    NumberAnimation {
                        duration: 200;
                        properties: "height,width,anchors.rightMargin,anchors.topMargin,opacity,contentY"
                    }
                }
            ]
        }
    }
}

image

image

The techniques demonstrated here to expand the delegate to fill the entire view can be employed to make an item delegate shift shape in a much smaller way. For instance, when browsing through a list of songs, the current item could be made slightly larger, accommodating more information about that particular item.

此处演示是扩展委托项,以填充整个视图的技术,可用于使项目委托,以较小的形状移动。例如,当浏览歌曲列表时,当前项目可能会稍微变大,以容纳有关该特定项目的更多信息。

 示例源码下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值