Qml組件化編程9-Model和View
簡介
本文是《Qml組件化編程》系列文章的第九篇,濤哥將教大家,Qml中Model和View的知識。
註:文章主要發布在濤哥的博客 和 知乎專欄-濤哥的Qt進階之路
目錄
(放個目錄方便大家預覽文章。破乎不支持目錄,這是從博客複製過來的,點擊會跳轉到博客)
- 簡介
- 界面、數據和邏輯分離
- Qt內置的Model-View
- 整數做model
- 關於delegate
- View與Repeater的區別
- ListModel
- 靜態ListModel
- 動態ListModel
- XmlListModel
- ObjectModel
- C++導出Model
- QList<T>
- QJsonArray
- QQmlPropertyMap
- ListView缺失的靈魂
- 搜索與排序
- 選中
- 拖拽
- 特效
界面、數據和邏輯分離
界面架構的理念發展的非常快,主要在Web技術的驅動下,就有這麼多架構:
MVC、MVP、 MVVM、 Flux、Redux。
濤哥並沒有深入的研究過這些架構,但只要抓住一些關鍵點就夠了:界面、數據和邏輯要分別處理,最終要能夠正確處理用戶輸入並顯示結果。
(也可能我做的都是小項目,沒有參與過大型的Web項目,眼界太低。歡迎大佬指點)
先來看一下Qt中提供的架構:
Model代表數據,View代表界面,這個Delegate嘛,就是用來定製View的顯示方式和Controll的調用,也應該算進View裡面去。
這樣看來Qt是M-V架構 ? 其實Qt算是MVC架構,這個Controll一般是自己實現的,和Model放在一起的。
不過Qt有信號/槽機制,在QtQuick中以屬性綁定的方式出現。信號/槽相當於Gof設計模式中的觀察者模式,也相當於Flux中的訂閱/發布模式。
濤哥按自己的實踐和理解,畫了一個Qt的Model-View架構草圖:
Qt內置的Model-View
View包括 ListView、TableView、TreeView這三種
(ComboBox也可以算作ListView)

對應的Model包括 ListModel、TableModel、TreeModel

Qt提供了一些抽象的Model類,需要自己去繼承並實現介面,也有一些可以直接用。
下圖是濤哥整理的Qt中model繼承關係:

其中的QStringListModel不是抽象類,可以直接用在ListView中。
QStandardItemModel也不是抽象類,可以直接用在任意一種View中。
在數據量大、有性能要求的地方,需要繼承QAbstractItemModel類,重新實現一個model。
對於性能要求不高的數據展示,會有一些更加方便、取巧的方式,接著往下看吧。
(友情提示:濤哥不關心QWidget,只說QtQuick/Qml)
整數做model
在ListView中,一個整數作為model,就可以創建多個delegate實例。
整數作為model,也可以用在GridView、Combobox、Repeater等需要model的地方。
<Qml組件化編程6-進度條定製>一文中,展示漸變效果,就用的整數作為model
import QtQuick 2.9
import QtQuick.Controls 2.5
Item {
anchors.fill: parent
GridView {
id: g
anchors.fill: parent
anchors.margins: 20
cellWidth: 160
cellHeight: 160
model: 180 //這裡的數據Model直接給個整數180
clip: true
property var invalidList: [27, 39, 40, 45, 71, 74, 105, 111, 119, 130, 135, 141] //這幾個是不能用的,看過運行報錯後手動列出來的。
delegate: Item{
width: 160
height: 160
Rectangle{
width: 150
height: 150
anchors.centerIn: parent
color: "white"
radius: 10
Text {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 2
text: index + 1
}
Rectangle {
width: 100
height: width
radius: width / 2
//編號在列表裡的,直接漸變賦值為null,就不會在Qml運行時報警告了
gradient: g.invalidList.indexOf(modelData + 1) < 0 ? modelData + 1 : null
anchors.centerIn: parent
anchors.verticalCenterOffset: 10
}
}
}
}
}
關於delegate
簡單說一下delegate:
上面GridView的 model設置為180,表示這個View要產生180個相同的構件實例,按照Grid的方式布局排列。
而delegate就相當於是一個模板,用來描述這180個相同的構件長啥樣。當然每個實例不可能完全長得一樣,我們可以通過
綁定delegate提供的內置屬性或其它屬性,達到"大同小異"的目的。
delegate中一般會提供一個index和一個modelData,詳細的說明需要參考相應的View文檔。
View與Repeater的區別
上面的GridView雖然會創建180個實例,但並不是一次創建全部的,而是只創建能看見的那幾個,否則會佔用很多CPU、內存和GPU資源。
而Repeater這種就是直接生成180個,並沒有做任何內置處理。
(Repeater也可以通過自己控制visible的方式,實現部分創建,後面濤哥有個RingView特效會用這種方式)
ListModel
Qml提供了ListModel這樣的一個封裝,可以直接在Qml中定義靜態的model
靜態ListModel
import QtQuick 2.0
ListModel {
id: fruitModel
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
}
然後在ListView中使用
ListView {
anchors.fill: parent
model: fruitModel
delegate: Item {
Text {
text: modelData.name
}
Text {
text: cost
}
}
}
第一個text通過modelData.name獲取到name值
第二個text直接用了cost,其實是modelData.cost省略了modelData。這種寫法在靜態的ListModel中是可以用的。
動態ListModel
ListModel還提供了一些動態修改數據的介面:

像append、 set、insert這些,參數里的jsobject就是js環境中的Object類型,可以參考JS手冊
這裡濤哥示例一下,動態添加元素
...
onClicked: {
var banana = new Object()
//或者這樣也行,按照js語法即可
//var bababa = Object.create(null)
banana["name"]="banana" //方括弧 + key的方式設置成員
babana.cost=15 //點+名字的方式設置成員
fruitModel.append(banana) //將創建的banana添加到model
}
...
更詳細的用法,可以參考 濤哥兩年前寫過的一個Qml表格編輯器
裡面有ListModel的JSON序列化和反序列化、動態增、刪、改,Ubuntu風格的查找、Redo、UnDo等大部分功能。
TaoQuick項目的插件機制,也是通過JSON動態添加Model元素。TaoQuick
XmlListModel
處理xml的model,可以方便地使用XPath。
XmlListModel {
id: feedModel
source: "http://rss.news.yahoo.com/rss/oceania"
query: "/rss/channel/item"
XmlRole { name: "title"; query: "title/string()" }
XmlRole { name: "link"; query: "link/string()" }
XmlRole { name: "description"; query: "description/string()" }
}
ObjectModel
可視對象的集合,做為model,連Delegate都省了
import QtQuick 2.0
import QtQml.Models 2.1
Rectangle {
ObjectModel {
id: itemModel
Rectangle { height: 30; width: 80; color: "red" }
Rectangle { height: 30; width: 80; color: "green" }
Rectangle { height: 30; width: 80; color: "blue" }
}
ListView {
anchors.fill: parent
model: itemModel
}
}
C++導出Model
除了以上這些,C++中導出的一些類型也可以作為數據model。
這裡的導出包括Q_PROPERTY和 Q_INVOKABLE函數的返回值、槽函數的返回值,以及
setContextProperty註冊到上下文的可用作model的類型。
一般使用Q_PROPERTY (本質上也是屬性的get函數返回值,在js中做了轉換)
QList<T>
QList<QString> 字元串列表,可以直接用,不用多說了。
QList<QObject*> QObject列表,List中的任意一個QObject有一些屬性變更時,都能通知到Qml。
QJsonArray
QJsonArray也是可以直接導出給ListView用,不過注意是只讀的。
QQmlPropertyMap
QQmlPropertyMap 是一個Map結構, 但是這個結構註冊後,Qml中可以直接用"點 + 名字"的方式訪問其中的數據
// create our data
QQmlPropertyMap ownerData;
ownerData.insert("name", QVariant(QString("John Smith")));
ownerData.insert("phone", QVariant(QString("555-5555")));
// expose it to the UI layer
QQuickView view;
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("owner", &ownerData);
view.setSource(QUrl::fromLocalFile("main.qml"));
view.show();
//main.qml
Text {
text: owner.name + " " + owner.phone
}
ListView缺失的靈魂
Qml這個ListView是殘缺不全的,很多功能都要自己實現。
搜索與排序
前面提到的QSortFilterProxyModel是一種在數據上實現排序和過濾的方法。
還有一種在View層實現搜索和過濾的方式,即DelegateModelGroup。(已經有案例在用,後續再放出代碼)
當然Qt5.12的ListView/TableView提供了行和列 隱藏控制的功能,View層做搜索會更方便一些。(還沒有實踐)
選中
按住Ctrl 再滑鼠點擊,多選, 再點擊一下反選。
按住Shift再滑鼠點擊,連選。
舊的QtQuick.Controls 1中也有一個ListView,帶SelectonModel功能,直接支持多選、反選。
5.12開始,QtQuick.Controls 1模塊被廢棄了,而Controls2中的ListView不帶這功能了。只能自己記鍵盤按鍵來模擬實現。
(順便吐槽一下,5.12直接把Controls 1的TreeView廢掉了,Controls 2又沒有TreeView。Controls 1的那個雖然還能用,程序跑起來就是一堆js 異常)
拖拽
拖動和放置功能也得自己做。
特效
ListView提供過度動畫,下拉刷新一類的效果很多人已經做了,濤哥就不重複了。
(濤哥正在給TaoQuick開發高級插件TaoEffect,將會包含大量酷炫特效組件,敬請期待)
轉載聲明
文章出自濤哥的博客 – 點擊這裡查看濤哥的博客
文章採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可, 轉載請註明出處, 謝謝合作 ? 濤哥
聯繫方式
作者: 武威的濤哥
開發理念: 弘揚魯班文化,傳承工匠精神
博客: https://jaredtao.github.io
github:https://github.com/jaredtao
微信: xsd2410421
QQ: 759378563
請放心聯繫我,樂於提供諮詢服務,也可洽談商務合作相關事宜。
推薦閱讀:
