signed

QiShunwang

“诚信为本、客户至上”

QML学习笔记5-模型-视图-代理

2020/12/26 11:27:42   来源:

QML中的MVC 概念

在QtQuick中 数据通过model-view(模型-视图)分离。 对于每一个view(视图) 每个数据元素的可视化都分给一个delegate(代理)
在QML中 model(模型)与view(视图)通过delelate(代理)连接起来。 功能划分如下:
model(模型)提供数据, 对于每个数据项 可能有多个值。
显示在view(视图)中的每项数据都是通过delegate(代理)来实现可视化、view(视图)的任务就是排列这些delegate(代理)
每个delegate(代理)将model item(模型项)的值显示给用户

基础模型:

最基本的分离数据与显示的方法是使用Repeater元素。它被用于实例化一组元素项,并且很容易与一个用于填充用户界面的定位器相结合。适合有限的静态数据。 
最基本的实现举例,repeater元素用于实现子元素的标号。 每个子元素都拥有一个可以方位的属性index, 用于区分不同的子元素
例子:

Column {
    id: root
    spacing: 2

    Repeater{
        model: ListModel{
            ListElement{name: "Mercury"; surfaceColor: "gray"}
            ListElement{name: "venus"; surfaceColor: "Yellow"}
            ListElement{name: "Earth"; surfaceColor: "blue"}
            ListElement{name: "Mars"; surfaceColor: "orange"}
        }

        delegate:Rectangle{
            width: 100
            height: 20

            radius: 3
            color: 'lightBlue'

            Text{
                anchors.centerIn: parent
                text: name
            }

            Rectangle{
                anchors.left: parent.left
                anchors.verticalCenter: parent.verticalCenter
                anchors.leftMargin: 4

                width: 16; height: 16
                radius: 8

                border.color: 'black'
                border.width: 1

                color: surfaceColor
            }
        }
    }
}

每个数据的入口都是模型,视图通过可视化代理来实现数据的可视化。将数据从显示中分离处理,一个模型可以是一个整数,提供给代理使用的索引值(index)。
如果javaScript数组被作为一个模型,模型数据变量(modelData)代表了数据的数据的当前索引。对于更加复杂的情况,每个数据项需要提供多个值,使用链表模型(ListModel)与链表元素(listElement)是一个更好的解决办法。对于静态模型,使用Reperater可以被用作视图,他可以非常方便的使用行(Row)、列(Column),栅格(Grid)、流(Flow)来创建用户界面。对于动态或者大的数据类型,使用ListView或者GridView更加合适,他们会在急需要时 动态的创建代理,减少场景下一次显示的元素的数量

动态视图(Dynamic Views)

QtQuick提供了ListView和GridView元素, 这两个都是基于Flickable(可滑动)区域的元素,因此用户可以放入更大的数据。同时,他们限制了同时实例化的代理数量
对于一个大型的模型,这意味这在同一个场景下只会加载有限的元素。

链表模型:(ListView)

控制滚动条方向属性为 orientation 
控制显示的方向为 layoutDirection
高亮元素控制: highlight属性
页眉与页脚 header 、 footer
例子:

Rectangle {
    id: root

    width: 300; height: 80
    color: "white"

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

        model: 100
        spacing: 5
        clip: true

        orientation: ListView.Horizontal
        //orientation: ListView.Vertical
        
        layoutDirection: Qt.LeftToRight
        delegate: numberDelegate
    }

    Component{
        id: numberDelegate

        Rectangle{
             width: 40; height: 40

             color: "lightgreen"

             Text{
                 color: "#000000"
                 anchors.fill: parent
                 font.pixelSize: 20


                 text:index
                 horizontalAlignment: Text.AlignHCenter
                 verticalAlignment: Text.AlignVCenter
             }
        }
    }
}

网格视图(GridView)

网格视图(GridView)与链表视图(ListView)非常相似.真正不同的地方在于栅格视图(GridView)使用了二维数组来存放元素,链表视图使用线性链表存放元素
与链表视图比较,网格视图不依赖与元素间隔和大小来配置元素。他们使用单元宽度(cellWidth)和单元高度(cellheight)属性来控制数组内二维元素的内容。 
单个元素从左上角开始一次放入单元格
例子:

Rectangle {
    width: 240; height: 300
    color: "white"

    GridView{
        anchors.fill: parent
        anchors.margins: 10
        clip: true
        model: 100

        cellWidth: 45; cellHeight: 45

        delegate: numberDelegate
    }

    Component{
        id:numberDelegate

        Rectangle{
            width: 40; height: 40
            color: "lightgreen"

            Text{
                anchors.centerIn: parent
                font.pixelSize: 10
                text: index+1
            }
        }
    }
}

代理(Delegate)

当使用模型与视图来自定义用户界面时,代理在创建显示时扮演了大量角色。在模型中的每个元素通过代理来实现可视化,用户真实可见的是这些代理的元素
每个代理访问到索引号或者绑定的属性,一些是来自数据模型,一些是来自视图。来自模型的数据将会通过属性传递到代理。 来自视图的数据将会通过属性传递视图中与代理相关的状态信息。
通常使用的视图绑定属性是ListView.isCurrentItem和ListView.view, 前者是一个布尔值,标识这个元素是否被选中,这个值是只读的,引用于当前视图。
通过访问视图,可以创建可复用的代理, 这些代理在被包含是会自动匹配视图的大小
例子:

Rectangle {
    width: 320
    height: 400

    ListView{
        id: listviewObj
        anchors.fill: parent
        anchors.margins: 10
        clip: true

        model: 100
        spacing: 5

        delegate: numberDelegate
    }

    Component{
        id: numberDelegate
        Rectangle{
             width: ListView.view.width
             height: 40

             color: ListView.isCurrentItem?"gray":"lightGray

             Text{
                 anchors.centerIn: parent
                 font.pixelSize: 10
                 text: index
             }

             MouseArea{
                 anchors.fill: parent
                 onClicked: listviewObj.currentIndex = index
             }
        }
    }
}

动画的添加与移除

在某些情况下,视图中显示的内容会随着时间而改变。由于模型属性的改变,元素会添加或者移除
为了方便使用,QML视图为每个代理绑定了两个信号, onAdd和onRemove. 使用动画链接它们,可以方便创建识别哪些内容被添加或者删除的动画。
例子:

Rectangle {
    width: 480
    height: 300

    color: "white"

    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 {
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.margins: 20

        height: 40

        color: "darkGreen"

        Text {
            anchors.centerIn: parent

            text: "Add item!"
        }

        MouseArea {
            anchors.fill: parent

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

        property int count: 9
    }

    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

            width: 40
            height: 40

            color: "lightGreen"

            Text {
                anchors.centerIn: parent

                font.pixelSize: 10

                text: number
            }

            MouseArea {
                anchors.fill: parent

                onClicked: {
                    if (!wrapper.GridView.delayRemove)
                        theModel.remove(index)
                }
            }

            GridView.onRemove: SequentialAnimation {
                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: SequentialAnimation {
                NumberAnimation { target: wrapper; property: "scale"; from: 0; to: 1; duration: 250; easing.type: Easing.InOutQuad }
            }
        }
    }
}