1. 程式人生 > >[Qt Quick入門] 基本元素初體驗

[Qt Quick入門] 基本元素初體驗

Qt Quick作為QML語言的標準庫,提供了很多基本元素和控制元件來幫助我們構建Qt Quick應用。這節我們簡要地介紹一些Qt Quick元素,如Rectangle、Item、Text、Button、Image、ButtonStyle、MouseArea等。

 

1 Rectangle

Rectangle用來繪製一個填充矩形,可以帶邊框,也可以不帶,可以使用純色填充,也可以使用漸變色填充,甚至還可以不填充而只提供邊框......

Rectangle有很多屬性,color屬性可以指定填充顏色,而gradient屬性則用來設定漸變色供填充使用,如果你同時指定了color和gradient,那麼gradient生效;如果你設定color屬性為transparent,那麼就可以達到只繪製邊框不填充的效果。

border.width和border.color分別用來指定邊框的寬度和顏色,radius設定圓角矩形。

示例▼

import QtQuick 2.0

Rectangle {
    width: 320
    height: 480
    color: Qt.rgba(0.4,0.6,0.4,1.0)
    border.width: 2
    border.color: "black"
    radius: 4
}

將上面的程式碼片段儲存到一個QML檔案中,然後在QML檔案目錄下使用qmlscene載入它來看效果。


2 顏色

關於顏色值,在QML 中可以使用顏色名字,如blue、red、green、transparent等,也可以使用“#RRGGBB”或者“#AARRGGBB”來指定,還可以使用Qt.rgba()、Qt.lighter()等方法來構造。color型別有r、g、b、a四個屬性,分別表示一個顏色值的alpha、red、green、blue四個成分。

示例▼

Rectangle {
    color: "red"
    //color: "#00AA00"
    //color: "#800000B0"
    //color: Qt.rgba(0.4,0.6,0.4,0.2)
}


3 漸變色

QML中漸變色的型別是Gradient,漸變色通過兩個或多個顏色值來指定,QML會自動在你指定的顏色之間插值,進行無縫填充。Gradient使用GradientStop來指定一個顏色值和它的位置(取值在0.0與1.0之間)。

示例▼

Rectangle {
    rotation: 90 //預設是垂直方向的線性漸變,其它方向可通過rotation旋轉獲得
    gradient: Gradient{
        GradientStop { position: 0.0; color: "black" }
        GradientStop { position: 0.33; color: "blue" }
        GradientStop { position: 1.0; color: "white" }
    }
}


4 Item

Item是Qt Quick中所有可視元素的基類,雖然它自己什麼也不繪製,但是它定義了繪製圖元所需要的大部分通用屬性,比如x、y、width、height、錨定(anchoring)和按鍵處理。

Item除了 x、y屬性,其實還有一個z屬性,用來指定圖元在場景中的Z序。2屬性的類 型是real,數值越小,圖元就越墊底(遠離我們);數值越大,圖元就越靠近我們。Item的屬性opacity可以指定一個圖元的透明度,取值在0.0到1.0之間。

雖然Item本身不可見,但你可以使用Item來分組其他的可檢視元。分組後可以通過Item的children或visibleChildren屬性來訪問子物件元素。示例如下:

示例▼

import QtQuick 2.0

Rectangle {
    width: 300
    height: 200

    //使用Item對Rectangle進行分組
    Item {
        id: gradientGroup

        //矩形子物件0
        Rectangle {
            x: 20
            y: 20
            width: 120
            height: 120
            gradient: Gradient {
                GradientStop { position: 0.0; color: "#202020" }
                GradientStop { position: 1.0; color: "#A0A0A0" }
            }
        }

        //矩形子物件1
        Rectangle {
            x: 160
            y: 20
            width: 120
            height: 120
            rotation: 90
            gradient: Gradient {
                GradientStop { position: 0.0; color: "#202020" }
                GradientStop { position: 1.0; color: "#A0A0A0" }
            }
        }

        //分組後可以通過Item的children或visibleChildren屬性來訪問子物件元素
        Component.onCompleted: {
            console.log("visible children: ",
                        gradientGroup.visibleChildren.length) //可見子物件長度,為2
            console.log("children: ",gradientGroup.children.length) //子物件長度,為2
            //訪問兩個子物件的x絕對座標
            for(var i=0; i<gradientGroup.children.length; i++){
                console.log("child ", i, " x=",gradientGroup.children[i].x)
            }
        }
    }
}

另外,你可能注意到了,x、y、width、height四個屬性結合起來,可以完成QtQuick應用的介面佈局,不過這種採用絕對座標的佈局方式,不太容易適應多種多樣的移動裝置解析度,也不太適應可變大小的視窗。可以採用一種全新的佈局方式:錨佈局。鋪佈局是通過Item的anchors屬性實現的。


5 錨佈局

anchors提供了一種方式,讓你可以通過指定一個元素與其他元素的關係來確定元素在介面中的位置。

你可以想象一下,每個Item都有7條不可見的鋪線:左(left)、水平中心(horizontalCenter)、 上(top)、下(bottom)、右(right)、垂直中心(verticalCenter)、基線(baseline)。如下圖所示。

上圖中,沒有標註基線,基線是用於定位文字的,你可以想象一行文字端坐基線的 情景。對於沒有文字的圖元,baseline和top—致。

使用anchors佈局時,除了對齊錨線,還可以指定上(topMargin)、下(bottomMargin)、 左(leftMargin)、右(rightMargin)四個邊的留白。如下圖所示。

而如果你想懶省事兒,也可以使用margins屬性將四個邊的留白置成一樣。示例程式如下:

import QtQuick 2.0

Rectangle {
    width: 300
    height: 200

    Rectangle {
        id: rect1
        anchors.left: parent.left
        anchors.leftMargin: 20
        anchors.top: parent.top
        anchors.topMargin: 20
        width: 100
        height: 100
        color: "red"
    }

    Rectangle {
        anchors.left: rect1.right
        anchors.leftMargin: 20
        anchors.top: rect1.top
        width: 100
        height: 100
        color: "blue"
    }
}

程式效果如下所示:

Item的anchors屬性,除了上面介紹的,還有一些,如centerln表示將一個Item居中放置到一個Item內;fill表示充滿某個Item……更多的請參考Item類的文件。


6 響應按鍵

前面提到Item可以處理按鍵,所有從Item繼承的元素都可以處理按鍵,比如Rectangle、 Button。Item通過附加屬性Keys來處理按鍵。

Keys物件是Qt Quick提供的、專門供Item處理按鍵事件的物件。它定義了很多針對特定按鍵的訊號,比如pressed和released訊號,一般地,你可以使用這兩個訊號來處理按鍵(請對照Qt C++ 中的keyPressEvent和keyReleaseEvent來理解)。它們有一個型別為KeyEvent、名字是event 的引數,包含了按鍵的詳細資訊。如果一個按鍵被處理,event.accepted應該被設定為true,以免它被繼續傳遞。

這裡舉一個簡單的例子,檢測到Escape和Back鍵時退出應用,檢測到數字鍵時,就通過Text來顯示對應的數字。示例程式如下:

import QtQuick 2.0

Rectangle {
    width: 300
    height: 200

    focus: true
    Keys.onEscapePressed: Qt.quit()
    Keys.onBackPressed: Qt.quit()
    Keys.onPressed: {
        switch(event.key) {
        case Qt.Key_0:
        case Qt.Key_1:
        case Qt.Key_2:
        case Qt.Key_3:
        case Qt.Key_4:
        case Qt.Key_5:
        case Qt.Key_6:
        case Qt.Key_7:
        case Qt.Key_8:
        case Qt.Key_9:
            event.accept = true
            keyView.text = event.key - Qt.Key_0;
            break;
        }
    }

    Text {
        id: keyView
        anchors.centerIn: parent
        font{ bold: true; pixelSize: 24}
        text: qsTr("text");
    }
}


7 Button

按鈕可能是GUI應用中最常用的控制元件了。QML中的Button和QPushButton類似,使用者點選按鈕會觸發一個clicked()訊號,在QML文件中可以為clicked()指定訊號處理器(onClicked),響應使用者操作。

要使用 Button,需要引入 import QtQuick.Controls 2.x() 先看一個簡單的示例,button_quit.qml,點選按鈕,退出應用。程式碼如下:

import QtQuick 2.0
import QtQuick.Controls 2.0

Rectangle {
    width: 300
    height: 200

    Button {
        anchors.centerIn: parent
        text: "Button"

        //onClicked為訊號處理器,處理clicked訊號
        onClicked: Qt.quit()
    }
}

checkable屬性設定Button是否可選。如果Button可選,checked屬性則儲存Button選中狀態。

iconName屬性設定圖示的名字,如果平臺的圖示主題中存在該名字對應的資源,Button 就可以載入並顯示它。iconSource則通過URL的方式來指定icon的位置。iconName屬性的優先順序高於iconSource。

isDefault屬性指定按鈕是否為預設按鈕,如果是預設的,使用者按Enter鍵就會觸發按鈕的clicked()訊號。(Qt Quick.Controls 2.0 已移除)

menu屬性允許你給按鈕設定一個選單(此時按鈕可能會出現一個小小的下拉箭頭),用 戶點選按鈕時會彈出選單。預設是null。(Qt Quick.Controls 2.0 已移除)

action屬性允許你設定按鈕的action, action可以定義按鈕的checked、text、tooltip、 iconSource等屬性,還可以繫結按鈕的clicked訊號。action屬性的預設值為null。

style屬性用來定製按鈕的風格,與它配套的有一個ButtonStyle類,允許你定製按鈕的背景和文字。(Qt Quick.Controls 2.0 已移除)

接下來看看如何使用ButtonStyle來定製按鈕外觀。


8 ButtonStyle

要使用 ButtonStyle,需要引入 QtQuick.Controls.Styles 1.x。(Qt Quick.Controls 2.0 已移除)

ButtonStyle 類有 background、control、label 三個屬性。background屬性的型別是Component,用來繪製Button的背景。label屬性的型別也是 Component,用於定製按鈕的文字。control屬性指向使用ButtonStyle的按鈕物件,你可以用它訪問按鈕的各種狀態。示例程式如下:

import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2

Rectangle {
    width: 300 
    height: 200

    Button {
        text: "Quit"
        anchors.centerIn: parent
        style: ButtonStyle {
            background: Rectangle {
                implicitWidth: 70;
                implicitHeight: 25;
                //按下按鍵時,邊框寬度增大為2
                border.width: control.pressed ? 2 : 1;
                //滑鼠覆蓋或者按下按鍵時,邊框顏色變為"green"
                border.color: (control.hovered || control.pressed)
                                ? "green" : "#888888";
            }
        }
        onClicked: Qt.quit();
    }
}

我通過給style屬性指定一個ButtonStyle物件來定製Button的風格。這個就地實現的 ButtonStyle物件,為background屬性指定一個Rectangle物件來定義按鈕的背景。我定義了背景的建議寬度和高度,根據按鈕的pressed屬性(control是實際按鈕的引用)來設定背景矩形的邊框粗細,而邊框顏色則隨著按鈕的hovered和pressed屬性而變化。

對於ButtonStyle,如果有多個按鈕同時用到,上面的方式就有點煩瑣了,此時我們可以 使用Component在QML文件內定義一個元件,設定其id屬性的值為btnStyle,然後在Button中設定style屬性時直接使用btnStyle。示例程式碼如下:

import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2

Rectangle {
    width: 300 
    height: 200

    Component {
        id: btnStyle
        ButtonStyle {
            background: Rectangle {
                implicitWidth: 70;
                implicitHeight: 25;
                //按下按鍵時,邊框寬度增大為2
                border.width: control.pressed ? 2 : 1;
                //滑鼠覆蓋或者按下按鍵時,邊框顏色變為"green"
                border.color: (control.hovered || control.pressed)
                                ? "green" : "#888888";
            }
        }
    }

    Button {
        text: "OK"
        style: btnStyle
        onClicked: Qt.quit();
    }

    Button {
        text: "Quit"
        style: btnStyle
        anchors.centerIn: parent
        onClicked: Qt.quit();
    }
}


9 Image

Image可以顯示一個圖片,只要是Qt支援的,比如JPG、PNG、BMP、GIF、SVG等都可以顯示。它只能顯示靜態圖片,對於GIF等格式,只會把第一幀顯示出來。如果要顯示動 畫,則可以使用 AnimatedSprite 或者 Animatedlmage。

Image的width和height屬性用來設定圖元的大小,如果沒有設定它們,那麼Image會使用圖片本身的尺寸。如果設定了 width和height,那麼圖片就可能會被拉伸來適應這個尺寸。

Image預設會阻塞式地載入圖片,如果要顯示的圖片很小,則沒什麼問題,如果解析度很高,那麻煩就來了。此時你可以設定asynchronous屬性為true來開啟非同步載入模式,在這種模式下Image使用一個執行緒來載入圖片,而你可以在介面上顯示一個等待圖示之類的小玩意兒來告訴使用者他需要等會兒。然後,當status (列舉值)的值為Image.Ready時再隱藏載入等待圖元。

Image支援從網路載入圖片。它的source屬性型別是url,可以接受Qt 支援的任意一種網路協議,比如http、ftp等。而當Image識別到你提供的source是網路資源 時,會自動啟用非同步載入模式。此時Image的progress (取值範圍是0.0〜1.0)、status (列舉 值)都會適時更新,你可以根據它們判斷何時結束載入等候提示介面。

顯示網路圖片

下面顯示網路上的圖片,在下載和載入前顯示一個轉圈圈的Loading圖示,圖片載入成功後隱藏Loading圖示,如果加載出錯,則顯示一個簡單的錯誤訊息。示例如下:

import QtQuick 2.2
import QtQuick.Controls 1.2

Rectangle {
    id: text
    width: 480
    height: 320

    //用來顯示一個等待圖元
    BusyIndicator {
        id: busy
        running: true
        anchors.centerIn: parent
        z: 2
    }

    Text {
        id: stateLabel
        visible: false
        anchors.centerIn: parent
        z: 3
    }

    Image {
        id: imageViewer
        //開啟非同步載入模式,專門使用一個執行緒來載入圖片
        asynchronous: true
        //圖片較大的情況下,指定不快取影象(cache預設為true)
        cache: false
        anchors.fill: parent
        //設定圖片的填充模式為“等比縮放”
        fillMode: Image.PreserveAspectFit
        onStatusChanged: {
            if (imageViewer.status === Image.Loading) {
                busy.running = true; //圖片為“載入狀態”,則顯示“等待圖元”
                stateLabel.visible = false
            }
            else if(imageViewer.status === Image.Ready)
                busy.running = false; //圖片為“準備好的狀態”,則不再顯示“等待圖元”
            else if(imageViewer.status === Image.Error) {
                busy.running = false;
                stateLabel.visible = true //圖片為“載入失敗狀態”,則顯示“Error”文字
                stateLabel.text = "Error"
            }
        }

        //上面都執行完了,再顯示圖片
        Component.onCompleted: {
            imageViewer.source = "https://www.cnblogs.com/images/cnblogs_com/linuxAndMcu/1348721/o_o_misaka.jpg"
        }
    }
}

Image物件,設定了 asynchronous屬性為true,不過對於網路資源Image預設非同步載入, 這個屬性不起作用,只有你想非同步載入本地資源時才需要設定它。cache屬性設定為false, 告訴Image不用快取圖片。fillMode屬性設定了等比縮放模式。

onStatusChanged是訊號處理器,Image的status屬性變化時會發射statusChanged()訊號。屬性變化觸發的訊號,對應的訊號處理器格式為on<property>Changed, 所以這裡的名字是onStatusChanged。在訊號處理器的程式碼塊中,我們通過Image物件的id訪問它的status屬性,根據不同的狀態來更新介面。


10 Busylndicator

Busylndicator用來顯示一個等待圖元,在進行一些耗時操作時你可以使用它來緩解使用者的焦躁情緒。

Busylndicator的running屬性是個布林值,為true時顯示。style屬性允許你定製Busylndicator。預設的效果就是一個轉圈圈的動畫。


11 圖片瀏覽器的第一個版本

import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Dialogs 1.2

Window {
    id: window
    visible: true
    width: 640
    height: 480
    minimumWidth: 480
    minimumHeight: 380
    title: qsTr("ImageViewer")

    BusyIndicator {
        id: busy
        running: false
        anchors.centerIn: parent
        z: 2
    }

    Text {
        id: stateLabel
        visible: false
        anchors.centerIn: parent
        z: 3
    }

    Image {
        id: imageViewer
        asynchronous: true
        cache: false
        anchors.fill: parent
        fillMode: Image.PreserveAspectFit
        onStatusChanged: {
            if (imageViewer.status === Image.Loading) {
                busy.running = true
                stateLabel.visible = false
            }
            else if(imageViewer.status === Image.Ready)
                busy.running = false
            else if(imageViewer.status === Image.Error) {
                busy.running = false
                stateLabel.visible = true
                stateLabel.text = "Error"
            }
        }
    }

    Button {
        id: openFile
        text: "open"
        anchors.left: parent.left
        anchors.leftMargin: 8
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 8
        style: ButtonStyle {
            background: Rectangle {
                implicitWidth: 70;
                implicitHeight: 25;
                border.width: control.pressed ? 2 : 1;
                border.color: (control.hovered || control.pressed)
                                ? "green" : "#888888";
            }
        }
        //按下按鈕,開啟檔案對話方塊
        onClicked: fileDialog.open()
        z: 4
    }

    Text {
        id: imageText
        anchors.left: openFile.right
        anchors.leftMargin: 8
        anchors.verticalCenter: openFile.verticalCenter
        font.pixelSize: 20
    }

    FileDialog {
        id: fileDialog
        title: "Please choose a ImageFile"
        nameFilters: ["Image Files (*.jpg *.png *.gif)"]
        onAccepted: {
            imageViewer.source = fileDialog.fileUrl
            var imageFile = new String(fileDialog.fileUrl)
            imageText.text = imageFile.slice(8)
        }
    }
}

在Open按鈕的onClicked訊號處理器中,呼叫FileDialog物件的open()方法讓使用者選擇檔案。當用戶選擇檔案後會觸發FileDialog的accepted訊號,我為它建立了onAccepted訊號處理器,在訊號處理器內設定imageViewer的source屬性來顯示圖片,同時設定imagePath 的text屬性來展示圖片檔案的路徑。程式效果如下圖所示:


12 FileDialog

FileDialog是Qt Quick中的檔案對話方塊,它可以用來選擇已有的檔案、資料夾,支援單 選、多選,也可以用來在儲存檔案或建立資料夾時讓使用者提供一個名字。

FileDialog的visible屬性的預設值為false,如果要顯示對話方塊,則需要呼叫open()方法或者設定此屬性為true。

selectExisting屬性的預設值為true,表示選擇已有檔案或資料夾;當其為false時,用於供使用者建立檔案或資料夾名字。

selectFolder屬性的預設值為false,表示選擇檔案;設定其為true,則表示選擇資料夾。

selectMultiple屬性的預設值為false,表示單選;設定其為true,則表示多選。當selectExisting為false 時,selectMultiple 應該為 false。

FileDialog還支援名字過濾功能,nameFilters用於設定一個過濾器列表。而selectedNameFilter則儲存使用者選擇的過濾器,或者用來設定初始的過濾器。

當用戶選擇了一個檔案時,fileUrl屬性儲存該檔案的路徑。如果使用者選擇了多個檔案, 該屬性為空。fileUrls屬性是一個列表,儲存使用者選擇的所有檔案的路徑。

folder屬性存放的是使用者選擇的(檔案所在的)資料夾的位置。

上面圖片瀏覽器例項中選擇圖片檔案的對話方塊也可以修改成這樣:

    FileDialog {
        id: fileDialog
        title: "Please choose a ImageFile"
        nameFilters: ["Image Files (*.jpg *.png *.gif)"]
        selectMultiple: true
        onAccepted: {
            imageViewer.source = fileDialog.fileUrls[0]
            var imageFile = new String(fileDialog.fileUrls[0])
            imageText.text = imageFile.slice(8)
        }
    }

做了上述修改後,可以一次選擇多個圖片檔案,也可以切換名字過濾器。