QML 的視圖組件(ListView、TableView、GridView)默認使用 ListModel 作為數據源,但這無法訪問本地文件系統或連接傳統 SQL 數據庫。工業級開發中,通常採用 C++ 實現數據模型,QML 負責視圖展示 的架構。本文系列將詳細講解如何基於 QAbstractItemModel 實現高性能的 TreeView 模型。
所有高級模型類(QAbstractTableModel、QSqlTableModel 等)均繼承自 QAbstractItemModel。要實例化自定義模型,必須繼承並實現以下 5 個純虛函數:
| 函數 | 作用 |
|---|---|
rowCount(parent) | 返回指定父節點下的行數 |
columnCount(parent) | 返回列數(QML 通常固定返回 1) |
index(row, column, parent) | 創建並返回模型索引,攜帶內部指針 |
parent(child) | 返回子節點的父節點索引 |
data(index, role) | 根據角色返回數據 |
與 Widget 不同,QML 模型不通過列訪問數據,而是通過 Role(角色)。視圖組件調用模型的 roleNames() 獲取可用角色映射,再以角色名綁定數據。
TableView {
TableViewColumn { role: "tagging"; title: ""; width: 50 }
TableViewColumn { role: "name"; title: "操作"; width: 80 }
}
QHash<int, QByteArray> roleNames() const override;
class SqlMenuEntry : public QAbstractItemModel, public QQmlParserStatus {
Q_OBJECT
public:
enum MenuEntryRoles {
idRole = Qt::UserRole + 1,
nameRole,
defaultEntryRole,
customEntryRole,
iconRole,
iconHoverRole
};
// ... 五大虛函數聲明 ...
QHash<int, QByteArray> roleNames() const override;
private:
QHash<int, QByteArray> mRoleNames;
QList<QHash<int, QVariant>> mRecords; // 二維數據存儲
};
QHash<int, QByteArray> SqlMenuEntry::roleNames() const {
return mRoleNames;
}
SqlMenuEntry::SqlMenuEntry(QObject *parent)
: QAbstractItemModel(parent) {
mRoleNames[nameRole] = "name";
mRoleNames[idRole] = "menuid";
mRoleNames[iconRole] = "icon";
mRoleNames[defaultEntryRole] = "default";
mRoleNames[iconHoverRole] = "iconHover";
}
int SqlMenuEntry::rowCount(const QModelIndex &parent) const {
return parent.isValid() ? 0 : mRecords.size(); // 扁平列表無子節點
}
int SqlMenuEntry::columnCount(const QModelIndex &) const { return 1; }
QModelIndex SqlMenuEntry::index(int row, int column, const QModelIndex &parent) const {
if (!parent.isValid() && row >= 0 && row < mRecords.size())
return createIndex(row, column, mRecords.at(row)); // 保存節點指針
return QModelIndex();
}
QModelIndex SqlMenuEntry::parent(const QModelIndex &) const { return QModelIndex(); } // 無父節點
QVariant SqlMenuEntry::data(const QModelIndex &index, int role) const {
if (index.isValid())
return mRecords[index.row()][role];
return QVariant();
}
// 添加數據
QHash<int, QVariant> row;
row[nameRole] = "name1";
row[iconRole] = "icon1";
mRecords.append(row);
// 註冊到 QML 類型系統
qmlRegisterType<SqlMenuEntry>("com.limutech.tv", 1, 0, "MenuEntryModel");
import com.limutech.tv 1.0
MenuEntryModel { id: menuEntryModel }
ListView { model: menuEntryModel; delegate: Item { Text { text: name } } }
rowCount() 獲取根節點行數columnCount() 獲取列數index() 獲取每行的 ModelIndex(內部指針已存入)roleNames() 獲取角色列表data(index, role) 獲取數據並交給 Delegate 顯示下一節將深入講解樹狀數據結構(TreeNode)的設計、parent() 的非空實現、以及數據增刪改的模型通知機制,敬請期待。