QML-计算器例程分析

研究了一段时间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"
}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章