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

郵箱: [email protected]

微信: xsd2410421

QQ: 759378563

請放心聯繫我,樂於提供諮詢服務,也可洽談商務合作相關事宜。


推薦閱讀:

TAG:Qt(C開發框架) | QtQuick | QML |