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 }
}
}
}
}