參考資料:html
參照 View-Model 模型,QML做爲 View,C++中的對象做爲 Model,實現業務邏輯和界面的分離。數組
經過這種方法,QML中能夠直接訪問註冊到上下文中的C++類實例,而且是註冊到QML的全局(具體是註冊到一個 QQuickView 或者 engine)。以自定義一個 Name 類,類包括一個 data 屬性爲例。併發
須要暴露給QML訪問的類須要有特殊的定義:app
/*name.h*/ #include <QObject> class Name : public QObject //繼承自QObject { Q_OBJECT//QObject宏 Q_PROPERTY(QString data READ data WRITE setData NOTIFY dataChanged) public: Name(QObject *parent = nullptr);//默認構造函數 Name(QString _name);//構造函數 QString data() const;//READ 接口 void setData(const QString& _data);//WRITE 接口 signals: QString dataChanged();//NOTIFY 信號(不需實現) private: QString m_data;//私有屬性 };
能夠經過右鍵項目->新建文件->C++ Class 來添加新類,繼承自 QObject 而且自動添加文件到項目中。函數
Warning:不要在 cpp 文件中直接定義類,由於 Q_OBJECT 宏須要通過 moc 處理,非
.h
文件不會被 moc 處理,編譯時出現「沒法識別的符號」錯誤ui
Q_PROPERTY(QString data READ data WRITE setData NOTIFY dataChanged)
這一行代碼定義了暴露給QML訪問的接口,這裏咱們提供的對象是一個 QString,READ 接口是一個名爲 data 的函數, WRITE 接口是一個名爲 setData 的函數,NOTIFY 接口用於通知的綁定,只有設置了 NOTIFY 接口,QML 才能自動與 C++ 中的屬性同步。這裏的命名方式最好與默認的統一。spa
/*name.cpp*/ #include "name.h" Name::Name(QObject *parent) : QObject(parent) {//默認構造函數 } Name::Name(QString _data) : m_data(_data) {//自定義構造函數,初始化私有對象m_data } QString Name::data() const { return m_data;//READ 接口實現,返回私有對象 } void Name::setData(const QString& _data) { if(_data != m_data){//WRITE 接口實現,更新m_data併發出信號 m_data = _data; emit dataChanged(); } }
在 setData 中,必須判斷數據是否更新,只有當數據真正改變時才發出信號,不然有無限遞歸的風險。code
而後是實例化一個 Name 類並註冊到上下文。註冊須要在讀取.qml
文件以前完成。htm
/*main.cpp*/ Name a_name("test"); QQmlApplicationEngine engine; QQmlContext* rootContex = engine.rootContext();//拿到engine的根上下文 rootContex->setContextProperty("name", QVariant::fromValue(a_name));//註冊 engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QML 中能夠讀取到接口返回的屬性,直接給屬性賦值,而且監聽到屬性變化。對象
/*main.qml*/ Rectangle { Text { text:name.data //至關於調用name.data()這個接口 Connections {//創建一個到NOTIFY接口的鏈接 target: name onDataChanged:{//對應NOTIFY接口 console.log("data has beed changed!"); } } } Button { text:"changeData" onClicked: { name.data = "hasChanged!"; } } }
當點擊按鈕時,Text 的文字會發生變化,同時控制檯輸出 「data has beed changed!」,閉環達成。
Connections 用來監聽事件,監聽的 target 是定義了 NOTIFY 的對象(注意 onDataChanged 這個 event handler 的駝峯命名法和原 NOTIFY 定義時的命名。
更常見的需求是,暴露一組對象的屬性,並經過 QML 中的 View 來自動渲染。例如咱們把 Name 類放進一個 QList 列表,做爲 Model 傳給 QML 中的 ListView。
/*main.cpp*/ QList<QObject*> NameList; NameList.append(new Name("name1")); NameList.append(new Name("name2"));//增長兩個Name對象在數組中 QQmlApplicationEngine engine; QQmlContext* rootContex = engine.rootContext();//拿到engine的根上下文 rootContex->setContextProperty("nameList", QVariant::fromValue(NameList));//註冊 engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
/*main.qml*/ ListView{ width: parent.width height: 300 model: nameList delegate: Rectangle { height: 30 width: parent.width color: "#000000" Text { text: model.data //調用每一個對象的data()方法 color: "#FFFFFF" Connections{ target: model //監聽這個對象 onDataChanged:{ console.log("changed!"); } } } } }
這樣就獲得了一個列表渲染。此時渲染出來的列表在註冊到上下文的時候就已經肯定,列表項Name
動態的修改會體現到界面上,可是動態增刪數組元素不會發生變化(由於 QList 自己並無 NOTIFY 接口)。
當前的 Name 類已經完備的實現了註冊到 QML 上下文中的接口,因此能夠在 C++ 中調用
qmlRegisterType<Name>("com.myapp.name", 1, 0, "Name"); //包名,大版本號,小版本號,類型名
進行註冊,在 QML 文件中加入引用後就能夠直接使用 Name 類以及其屬性。
import com.myapp.name 1.0 Name { data: "Foo" }
與直接放入 QList 不一樣,經過更高級的封裝能夠實現動態綁定的列表渲染。避免使用QQmlListProperty
這個方法,文檔有問題且相關資料少
參考資料
樸素的 C++ 線性表類型(數組或者 Vector 模板類等等)經過封裝就能夠成爲被 QML 直接訪問的 Model。封裝成的類能夠是繼承自 QAbstractListModel 或者更復雜的 QAbstractTableModel。關鍵在於繼承後實現幾個做爲 QML 調用接口的虛函數(完整的虛函數表參照文檔):
/*必須實現的虛函數*/ int rowCount(const QModelIndex &parent) const;//返回數據行數 QVariant data(const QModelIndex &index, int role) const;//返回根據index和role請求的數據 QHash<int, QByteArray> roleNames() const;//返回數據別名
經過實現更多的虛函數能夠完成更復雜的面向 QML 功能。
假定咱們有一個 QList 內部的每一個元素都是 Name 類型,注意 Name 已經完成了對 QML 訪問所必須的封裝。因而一個可在QML中渲染的 Name 類型的線性表封裝成的類應該長這樣:
/*namelist.h*/ #include <QAbstractListModel> #include <QVariant> #include <QDebug> #include "name.h" class NameList : public QAbstractListModel { Q_OBJECT public: enum datatype { type1 = 0 }; NameList(QObject *parent); NameList(){ addName("test1"); addName("test2"); } /*必須實現的虛函數 供QML引擎調用*/ int rowCount(const QModelIndex &parent) const;//返回數據行數 QVariant data(const QModelIndex &index, int role) const;//返回所求的數據 QHash<int, QByteArray> roleNames() const;//返回數據別名 /*其餘接口*/ Q_INVOKABLE bool pushData(QString a_name); private: QList<Name*> _NameList;//被封裝的數組 };
首先類內聲名了一個枚舉類型,每一個類型對應數據項中被訪問的一個屬性。Name 類只有一個 data 屬性,因此只定義了一種類型。
而後是rowCount(const QModelIndex &parent)
,QML引擎查詢列表時經過這個函數取得列表項的數量。
QVariant data(const QModelIndex &index, int role)
是QML引擎用來訪問每一個列表項的接口,訪問的時候會經過index
代表索引,role
代表查找的屬性(對應枚舉類型datatype
)。
QHash<int, QByteArray> roleNames()
返回role的別名(暫時不是特別重要)。
/*namelist.cpp*/ #include <QQmlListProperty> #include <QList> #include "namelist.h" NameList::NameList(QObject *parent) { } int NameList::rowCount(const QModelIndex &parent) const { return _NameList.count();//返回私有列表數據量 } QVariant NameList::data(const QModelIndex &index, int role) const { qDebug() << role; int row = index.row();//index包含.row()和.count()等屬性 return QVariant::fromValue(_NameList.at(row));//數據項包裝成QVariant返回 } QHash<int, QByteArray> NameList::roleNames() const { QHash<int, QByteArray> d; d[datatype::type1] = "Foo";//給tpye1設置別名 return d; } bool NameList::pushData(QString a_name) { Name* cache = new Name(a_name); beginInsertRows(QModelIndex(), _NameList.count(), _NameList.count()); _NameList.append(cache); endInsertRows(); return true; }
列表被封裝之後,在進行增刪的時候須要先調用beginInsertRows(QModelIndex, int , int)
,第一個參數對應 Model 數據,經過QModelIndex()
獲得這個Model的虛擬rootItem
;後兩個參數表明所改動的行數範圍:例如在第二行加入3個數據,則兩個參數分別是\( (2, 4) \). 修改完成後還要調用endInsertRows()
聲名修改完畢。
/*main.cpp*/ NameList theList;//實例化一個類 QQmlApplicationEngine engine; QQmlContext* rootContex = engine.rootContext(); rootContex->setContextProperty("namelist", &theList);//註冊到上下文 engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
/*main.qml*/ ListView{ width: parent.width height: 300 model: namelist //把抽象類做爲model delegate: Rectangle { height: 30 width: parent.width color: "#999999" Text { text: model.modelData.name //name是每一個item的屬性 color: "#FFFFFF" } Button { anchors.right: parent.right width: 50 height: 30 font.family: "FontAwesome" font.pixelSize: 24 text: "\uf019" onClicked: { model.modelData.name = textField.text;//能夠直接給item修改屬性 } } } }