研究了一段时间QML,现在对Qt中的一个计算器范例的代码进行分析,并总结一下前面学习的内容.Qt这种语言大多数还是被用于嵌入式设备上,而QML则是专为嵌入式设备而生的.Qt在桌面开发上占据的比例很小,而且已被Nokia出售,未来的前景如何谁也不好说.但Qt确实很棒,祝福一下吧,如果以后Qt支持android和苹果的开发了,在继续深入研究.
上图是运行效果图,界面风格确实很漂亮.鼠标点击按钮后还有一个变灰的反应,整体来说界面简洁大气.而且实现了计算器的基本功能,这里要说明的是,所有功能都是由QML独立完成的,没有任何qt插件参与.而且调整界面的尺寸后,还会使界面发生旋转.这样的功能这样的界面效果要是使用Qt或Delphi,VC来实现的话,相信还是有一点的工作量的.正如前面翻译的文章中所说的那样,QML适合于界面上有大量简单动态元素的情形.像这种计算器程序或时钟程序使用QML实现就太方便了.
在总结一下以前翻译的几篇文章中的要点:QML中的核心是属性绑定,对象的属性发生了变化不一定就一定有函数在给属性赋值.可能是其他的属性与其有绑定关系,当这些属性发生变化时,QML引擎会自动为属性重新计算值.动画效果的实现依靠State和Transition.闲话少说,直接分析代码吧.
计算器程序的组织结构
在core目录中,定义了按钮组件Button和显示计算器输入信息及计算结果的Display组件.core/images目录中是按钮的图片和Display组件的背景图片.
还有一个qmldir文件,这个文件没有后缀.其中存储了目录中组件的名称和位置.
按钮组件
先来看看Button.qml文件的定义.这个文件定义了按钮组件,为了分析方便,我将原码直接拷贝过来,每行代码后面加上注释.
import QtQuick 1.0 //导入QML基本元素 版本号为1.0
BorderImage { //声明一个BorderImage元素 BorderImage一般用来作为边界图像.这里直接用来显示按钮图像
id: button //设置其唯一标识
property alias operation: buttonText.text //定义一个属性别名供外部使用,当给operation赋值或读取operation时,实际上在操作buttonText.text的值 buttonText元素在后面定义
property string color: "" //定义字符串属性color,默认值为""
signal clicked //定义一个信号,这里的信号和Qt中的信号概念上相同,用法上也一致
//source属性指定其图片的地址,注意这里使用了属性绑定,最终的值与color有关,
//如果color的值发生了变化,source的值自动变化.最终计算的source值正好是上图中几个按钮的背景图片的名称
source: "images/button-" + color + ".png"; clip: true
border { left: 10; top: 10; right: 10; bottom: 10 } //设置边界 定义了图像距离外边框的距离 这里上下左右都空闲10个像素
Rectangle { //声明了一个矩形,这个矩形在鼠标点击的时候设置opacity为0.4,使按钮变灰.但不会影响按钮上显示的文字,因为文字是在其下方声明的.
id: shade //设置唯一标示
anchors.fill: button; /*完全平铺到button上*/radius: 10;/*定义圆角半径*/ color: "black"; opacity: 0/*定义了透明度,0为完全透明,1为完全不透明*/
}
Text { //声明按钮上的文本
id: buttonText //设置唯一标识 上面定义属性property alias operation: buttonText.text就引用了这个标识.Text上显示的文本就是text属性的值
anchors.centerIn: parent;/*居中显示*/ anchors.verticalCenterOffset: -1/*垂直居中偏移-1像素*/
font.pixelSize: parent.width > parent.height ? parent.height * .5 : parent.width * .5 //计算字体大小,为按钮宽高最小值的一半
style: Text.Sunken;/*设置文本风格*/ color: "white"; styleColor: "black"; smooth: true
}
MouseArea { //设置鼠标响应区域
id: mouseArea
anchors.fill: parent //整个按钮区域都可响应鼠标
onClicked: {
doOp(operation) //定义doOp函数,注意doOp在calculator.qml中定义,这个qml引用了Button.qml,由于qml是声明式的,因此可先引用后声明(定义).
button.clicked() //触发button的click信号
}
}
states: State { //定义State实现动画效果 这个State实现当mouseArea是鼠标按下状态时,修改shade的属性opacity的值为0.4,也就是当按钮被按下时看到一层淡淡的灰色.
name: "pressed"; when: mouseArea.pressed == true //when关键字定义状态触发的条件
PropertyChanges { target: shade; opacity: .4 } //改变shade的opacity属性
}
}
Display组件
import QtQuick 1.0 //导入1.0版本的QtQuick模块
BorderImage { //定义显示背景图片元素
id: image //唯一标识
property alias text : displayText.text //属性别名 设置text就是给displayText.text赋值
property alias currentOperation : operationText //属性别名 这是一个Text元素类型的属性
source: "images/display.png" //设备背景图片
border { left: 10; top: 10; right: 10; bottom: 10 } //设置图片与边框的距离
Text {
id: displayText
anchors { //定位
right: parent.right;/*右侧与父对象的右侧对齐*/ verticalCenter: parent.verticalCenter;/*垂直居中*/ verticalCenterOffset: -1/*垂直偏移量-1 显示稍偏下*/
rightMargin: 6; /*右边界间隔6个像素*/left: operationText.right/*左侧与operationText的右侧对齐*/
}
font.pixelSize: parent.height * .6; text: "0"; horizontalAlignment: Text.AlignRight; elide: Text.ElideRight
color: "#343434"; smooth: true; font.bold: true
}
Text {
id: operationText
font.bold: true;/*粗体*/ font.pixelSize: parent.height * .7
color: "#343434"; smooth: true
anchors { left: parent.left;/*靠左显示*/ leftMargin: 6;/*左侧边距6像素*/ verticalCenterOffset: -3; verticalCenter: parent.verticalCenter }
}
}
Display组件定义了一个背景图,上面有两个Text,这两个Text一个靠左,一个靠右,平铺在Display组件上,而且两个Text直接具有描点关系:anchors{displayText.left: operationText.right}.displayText的左侧总与operationText的右侧相连.说明在改变大小时operationText不变,而displayText是可伸展的.
calculator定义
两个共用组件介绍完了,现在看看calculator.qml.这是计时器的定义文件.
import QtQuick 1.0
import "Core" //导入Core目录中定义的组件 引擎查找目录中的qmldir文件(无后缀),根据其中的内容导入定义的组件.
import "Core/calculator.js" as CalcEngine //导入javaScript文件内容 也可作为一个组件来看,并定义了组件别名,下面使用文件中定义的函数时可用:别名.方法名
Rectangle {
id: window
width: 360; height: 480 //定义窗口尺寸
color: "#282828"
property string rotateLeft: "\u2939"
property string rotateRight: "\u2935"
property string leftArrow: "\u2190"
property string division : "\u00f7"
property string multiplication : "\u00d7"
property string squareRoot : "\u221a"
property string plusminus : "\u00b1"
function doOp(operation) { CalcEngine.doOperation(operation) } //定义了个函数,供下面调用.这个函数又调用了js文件中的doOperation函数,注意参数operation是按钮上的文字内容.
Item {
id: main
state: "orientation " + runtime.orientation //runtime.orienttation返回界面的显示方向. 如果方向改变,就会重新设置state的值,其属性也会按state定义的相应更改.
property bool landscapeWindow: window.width > window.height
property real baseWidth: landscapeWindow ? window.height : window.width //取宽高中最小的那个值
property real baseHeight: landscapeWindow ? window.width : window.height //取宽高中最大的那个值
property real rotationDelta: landscapeWindow ? -90 : 0
rotation: rotationDelta //根据窗口宽与高的大小来调整旋转角度,只用一行代码搞定界面旋转
width: main.baseWidth
height: main.baseHeight
anchors.centerIn: parent
//定义一个Column元素,单列排布其中的子元素.上面是Display 下面是多个按钮的区域
Column {
id: box; spacing: 8
anchors { fill: parent; topMargin: 6; bottomMargin: 6; leftMargin: 6; rightMargin: 6 }
//显示Display组件
Display {
id: display
width: box.width-3
height: 64
}
//定义按钮区域 应使用Column元素声明 其中的子元素垂直分布 共分三个区域按钮 界面中紫色,绿色,及下面的其他按钮三个部分
Column {
id: column; spacing: 6
property real h: ((box.height - 72) / 6) - ((spacing * (6 - 1)) / 6)//计算出每个按钮的高度
property real w: (box.width / 4) - ((spacing * (4 - 1)) / 4) //计算出每个按钮的宽度
//定义紫色按钮区域 按钮之所以显示为紫色,因为Button的color属性设置为purple,在Button按钮组件定义中,其背景图片的source属性与color绑定,确定了显示哪个图片
Row { //Row元素定义一行,其中包含的元素水平布局
spacing: 6
Button { width: column.w; height: column.h; color: 'purple'; operation: "Off" }
Button { width: column.w; height: column.h; color: 'purple'; operation: leftArrow }
Button { width: column.w; height: column.h; color: 'purple'; operation: "C" }
Button { width: column.w; height: column.h; color: 'purple'; operation: "AC" }
}
//定义绿色按钮区域
Row {
spacing: 6
property real w: (box.width / 4) - ((spacing * (4 - 1)) / 4)
Button { width: column.w; height: column.h; color: 'green'; operation: "mc" }
Button { width: column.w; height: column.h; color: 'green'; operation: "m+" }
Button { width: column.w; height: column.h; color: 'green'; operation: "m-" }
Button { width: column.w; height: column.h; color: 'green'; operation: "mr" }
}
//定义其他按钮
Grid { //Grid元素定义一个网格,其中的元素都占据一个小格
id: grid; rows: 5;/*指定网格的行数*/ columns: 5;/*指定网格的列数*/ spacing: 6
property real w: (box.width / columns) - ((spacing * (columns - 1)) / columns)
Button { width: grid.w; height: column.h; operation: "7"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: "8"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: "9"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: division }
Button { width: grid.w; height: column.h; operation: squareRoot }
Button { width: grid.w; height: column.h; operation: "4"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: "5"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: "6"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: multiplication }
Button { width: grid.w; height: column.h; operation: "x^2" }
Button { width: grid.w; height: column.h; operation: "1"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: "2"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: "3"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: "-" }
Button { width: grid.w; height: column.h; operation: "1/x" }
Button { width: grid.w; height: column.h; operation: "0"; color: 'blue' }
Button { width: grid.w; height: column.h; operation: "." }
Button { width: grid.w; height: column.h; operation: plusminus }
Button { width: grid.w; height: column.h; operation: "+" }
Button { width: grid.w; height: column.h; operation: "="; color: 'red' }
}
}
}
//定义状态,main元素的state属性指定为如下状态名称时,其属性值就会发生改变 通常为了具有动画效果,states要与transitions配合使用
states: [
State {
name: "orientation " + Orientation.Landscape
PropertyChanges { target: main; rotation: 90 + rotationDelta; width: main.baseHeight; height: main.baseWidth }
},
State {
name: "orientation " + Orientation.PortraitInverted
PropertyChanges { target: main; rotation: 180 + rotationDelta; }
},
State {
name: "orientation " + Orientation.LandscapeInverted
PropertyChanges { target: main; rotation: 270 + rotationDelta; width: main.baseHeight; height: main.baseWidth }
}
]
//定义动画效果
transitions: Transition {
SequentialAnimation { //定义一个顺序执行的动画
RotationAnimation { direction: RotationAnimation.Shortest; duration: 300; easing.type: Easing.InOutQuint } //旋转动画效果属性
NumberAnimation { properties: "x,y,width,height"; duration: 300; easing.type: Easing.InOutQuint } //在x,y,width,height属性发生变化时的动画属性
}
}
}
}
算法
计时器的算法定义在一个单独的JavaScript文件中.
var curVal = 0
var memory = 0
var lastOp = ""
var timer = 0
function disabled(op) {
if (op == "." && display.text.toString().search(/\./) != -1) {
return true
} else if (op == squareRoot && display.text.toString().search(/-/) != -1) {
return true
} else {
return false
}
}
function doOperation(op) {
if (disabled(op)) {
return
}
if (op.toString().length==1 && ((op >= "0" && op <= "9") || op==".") ) {
if (display.text.toString().length >= 14)
return; // No arbitrary length numbers
if (lastOp.toString().length == 1 && ((lastOp >= "0" && lastOp <= "9") || lastOp == ".") ) {
display.text = display.text + op.toString()
} else {
display.text = op
}
lastOp = op
return
}
lastOp = op
if (display.currentOperation.text == "+") { //已经按下了+号
display.text = Number(display.text.valueOf()) + Number(curVal.valueOf())
} else if (display.currentOperation.text == "-") {
display.text = Number(curVal) - Number(display.text.valueOf())
} else if (display.currentOperation.text == multiplication) {
display.text = Number(curVal) * Number(display.text.valueOf())
} else if (display.currentOperation.text == division) {
display.text = Number(Number(curVal) / Number(display.text.valueOf())).toString()//开始计算
} else if (display.currentOperation.text == "=") {
}
if (op == "+" || op == "-" || op == multiplication || op == division) {
display.currentOperation.text = op
curVal = display.text.valueOf()
return
}
curVal = 0
display.currentOperation.text = ""
if (op == "1/x") {
display.text = (1 / display.text.valueOf()).toString()
} else if (op == "x^2") {
display.text = (display.text.valueOf() * display.text.valueOf()).toString()
} else if (op == "Abs") {
display.text = (Math.abs(display.text.valueOf())).toString()
} else if (op == "Int") {
display.text = (Math.floor(display.text.valueOf())).toString()
} else if (op == plusminus) {
display.text = (display.text.valueOf() * -1).toString()
} else if (op == squareRoot) {
display.text = (Math.sqrt(display.text.valueOf())).toString()
} else if (op == "mc") {
memory = 0;
} else if (op == "m+") {
memory += display.text.valueOf()
} else if (op == "mr") {
display.text = memory.toString()
} else if (op == "m-") {
memory = display.text.valueOf()
} else if (op == leftArrow) {
display.text = display.text.toString().slice(0, -1)
if (display.text.length == 0) {
display.text = "0"
}
} else if (op == "Off") {
Qt.quit();
} else if (op == "C") {
display.text = "0"
} else if (op == "AC") {
curVal = 0
memory = 0
lastOp = ""
display.text ="0"
}
}