使用C++實現QML的TreeView Model (一)
QML中的數據訪問組件如ListView、TableView、GridView通常使用ListModel做為數據提供者,這種應用有相當大局限性,如無法訪問本地文件系統、無法連接到傳統的SQL數據庫,所以通常在使用中都是通過C++實現數據訪問,通過QML進行數據展示和編輯,常用的數據模型組件有QAbstractItemModel、QAbstractTableModel、QSQLTableModel等。所有的高級Model組件都繼承自QAbstractItemModel,只要了解QAbstractItemModel的接口函數和運作機理,就可以了解QT的Model/View機制實現方式。
QAbstractItemModel是一個抽象類,要實例化QAbstractItemModel必須繼承並至少實現以下5個方法:
int rowCount(const QModelIndex &parent=QModelIndex()) const;
int columnCount(const QModelIndex &parent=QModelIndex()) const;
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const;
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const;
QModelIndex parent(const QModelIndex &child) const;
與 QWidget組件不同的是,在QML的數據模型中,並不通過列(Column)進行數據訪問,而是通過Role進行數據訪問,例如:
- TableView{
- id:tableView1
- anchors.fill: parent
- TableViewColumn{
- width:50
- title:""
- role:"tagging"
- }
- TableViewColumn{
- width:80
- title:"操作"
- role:"name"
- }
- }
TableViewColumn是TableView的列定義,TableViewColumn通過role屬性定義來向model獲取數據,TableView會通過調用model的roleNames()方法來獲取model可用的role。所以,除了必須要實現的5個虛函數外,還必須重新實現roleNames()來告訴View有哪些role是可用的,roleNames()的原型如下:
- QHash roleNames() const;
以下是一個完整的Model類定義:
- classSqlMenuEntry:public QAbstractItemModel,public QQmlParserStatus
- {
- Q_OBJECT
- public:
- explicit SqlMenuEntry(QObject *parent=0);
- ~SqlMenuEntry();
- enum MenuEntryRoles{idRole=Qt::UserRole+1,nameRole,defaultEntryRole,customEntryRole,iconRole,iconHoverRole};
- int rowCount(const QModelIndex &parent=QModelIndex()) const;
- int columnCount(const QModelIndex &parent=QModelIndex()) const;
- QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const;
- QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const;
- QModelIndex parent(const QModelIndex &child) const;
- QHashroleNames() const;
- private:
- QHash mRoleNames;
- QList> mRecords; //真正的數據保存在這裡,QList只能保存二維數據沒辦法保存樹狀節點,這裡僅僅是例子
- };
roleNames()的實現相當簡單:
- QHash SqlMenuEntry::roleNames() const
- {
- return mRoleNames;
- }
mRoleNames可以在類構造函數中進行初始化:
- SqlMenuEntry::SqlMenuEntry(QObject *parent)
- :QAbstractItemModel(parent)
- {
- mRoleNames[nameRole] = "name";
- mRoleNames[idRole] = "menuid";
- mRoleNames[iconRole] = "icon";
- mRoleNames[defaultEntryRole] = "default";
- mRoleNames[iconHoverRole] = "iconHover";
- }
在QML中就可以通過"name"、"menuid"、"icon"對數據進行訪問:
- ListView{
- model:MenuEntryModel{ }
- delegate:Item{
- Column{
- Text{text:name}
- Text{text:icon}
- }
- }
- }
如果僅為二維表提供數據,那麼根據以上幾個接口函數的名稱就可以簡單的實現數據供給View視圖,其中:
- int SqlMenuEntry::rowCount(const QModelIndex &parent) const
- {
- return mRecords.size();
- }
- int SqlMenuEntry::columnCount(const QModelIndex &parent) const
- {
- return 1; //QML不使用列獲取數據,默認返回一列,不返回1例的話,View控件會認為表是空表,不獲取數據
- }
- QModelIndex SqlMenuEntry::index(int row, int column, const QModelIndex &parent) const
- {
- if((row >= 0)&&(row < mRecords.size()))
- {
- return createIndex(row,column);
- }
- return QModelIndex(); //返回一個無效的空索引
- }
- QModelIndex SqlMenuEntry::parent(const QModelIndex &child) const
- {
- return QModelIndex(); //二維表中的行沒有parent節點
- }
- QVariant SqlMenuEntry::data(const QModelIndex &index, int role) const
- {
- if(index.isValid)
- {
- return mRecords[index.row()][role];
- }
- }
mRecords中的數據可以按需求生成,如通過QSqlQuery組件從數據庫服務器獲取,獲取添加數據:
- QHash row;
- row[nameRole] = "name1";
- row[iconRole] = "icon1";
- mRecords.append(row);
實現後的model類可以通過
- qmlRegisterType("com.limutech.tv",1,0,"MenuEntryModel");
進行注冊,注冊後的類可以在QML生成實例:
- import com.limutech.tv 1.0
- MenuEntryModel{
- id:menuEntryModel
- }
- ListView{
- model:menuEntryModel
- ...
- }
View組件獲取數據的流程大概如下:
1、View調用rowCount(constQModelIndex &parent)並傳遞一個空的parent獲取根節點的行數;
2、View調用columnCount(const QModelIndex &parent)並傳遞一個空的parent獲取根節點的列數;
3、View對各行列枚舉調用index(int row, int column, const QModelIndex &parent)交傳行號、列號和空的parent獲取根節點QModelIndex;
4、繼續以返回的modelIndex為parent獲取每行的rowCount和columnCount,如果大於0則該節點還有子節點;
5、調用roleNames返回可用的role列表
6、以返回的modelIndex和role為參數,調用data獲取數據並使用相應的delegate進行顯示
所以要顯示一個樹狀列表,需要對二維表模型進行完善,處理parent不為空的情況,同時,模型應該可以存儲樹狀數據,在下一節我將繼續分享層次模型的實現方式和涉及數據修改的一些實現。