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

}

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