Qt Quick 作為 QML 語言的標准庫,提供了很多基本元素和控件來幫助我們構建 Qt Quick 應用,這節我們簡要地介紹一些 Qt Quick 元素。
一、 基本可視化項
1.1 Item
Item(基本的項元素)是 Qt Quick 中所有可視元素的基類,雖然它自己什么也不繪制,但是它定義了繪制圖元所需要的大部分通用屬性,比如 x、y、width、height、錨定(anchoring)和按鍵處理。
-
Item 除了 x、y 屬性,其實還有一個 z 屬性,用來指定圖元在場景中的 Z 序。z 屬性的類 型是 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 屬性實現的。
1.2 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 加載它來看效果。
1.3 Text
Text 可以顯示純文本或者富文本。
-
可以使用Html標記:text: “<b>HELLO</b>” 。
-
換行:wrapMode屬性,沒有設置,則單行。
-
horizontalAlignment:水平對齊方式; verticalAlignment: 垂直對齊方式。
-
字體:font
- font.blod:true - 加粗,false - 不加粗
- font.family:字體族
- font.italic:true - 斜體 ,false - 不斜體
Text {
id: coloredText;
text: "Hello World!";
font.pixelSize: 32;
}
1.4 color
color(顏色元素)在 QML 中可以使用顏色名字,如 blue、red、green、transparent 等,也可以使用 “#RRGGBB” 或者 “#AARRGGBB” 來指定,還可以使用 Qt.rgba()、Qt.lighter() 等方法來構造。color 類型有 r、g、b、a 四個屬性,分別表示一個顏色值的 red、green、blue、alpha 四個成分。
Rectangle {
color: "red"
// color: "#00AA00"
// color: "#800000B0"
// color: Qt.rgba(0.4,0.6,0.4,0.2)
}
1.5 Gradient(漸變色)
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" }
}
}
1.6 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屬性,根據不同的狀態來更新界面。
1.7 Busylndicator(等待圖元)
Busylndicator 用來顯示一個等待圖元,在進行一些耗時操作時你可以使用它來緩解用戶的焦躁情緒。
Busylndicator 的 running 屬性是個布爾值,為 true 時顯示。style 屬性允許你定制 Busylndicator。默認的效果就是一個轉圈圈的動畫。
二、 基本的交互項
2.1 Keys
前面提到 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");
}
}
2.2 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 來定制按鈕外觀。
2.3 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
對於 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();
}
}
2.4 MouseArea(鼠標句柄交互 )
MouseArea 元素的一個很典型的用法是和一個可視的 item 一起用,處理這個 item 的鼠標響應。
在下例中我們將 MouseArea 放到 Rectangle 中,當單擊 Rectangle 區域中時,Rectangle 顏色會變成紅色。
import QtQuick 2.0
import QtQuick.Controls 1.2
Rectangle {
width: 100; height: 100
color: "green"
MouseArea {
anchors.fill: parent
onClicked: { parent.color = 'red' }
}
}
很多時候,MouseArea 區域會傳遞一個鼠標事件作為參數,這個參數中包含了很多鼠標事件信息,例如,
單擊的位置,具體按下的一個鼠標左鍵還是右鍵,以及一些鍵盤按鍵信息。在下面的例子中,當 Rectangle
區域被右鍵單擊時會觸發改變顏色。
Rectangle {
width: 100; height: 100
color: "green"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button == Qt.RightButton)
parent.color = 'blue';
else
parent.color = 'red';
}
}
}
對於其他鍵盤按鍵的處理,請參考Keys元素的介紹。MouseArea是一個可見的item,但它本身並不顯示什么。
三、其它
3.1 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)
}
}
做了上述修改后,可以一次選擇多個圖片文件,也可以切換名字過濾器。
四、實例 - 圖片瀏覽器V1.0
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 屬性來展示圖片文件的路徑。程序效果如下圖所示: