Qt 是目前最先進、最完整的跨平臺C++開發工具。它不僅完全實現了一次編寫,所有平臺無差別運行,更提供了幾乎所有開發過程中需要用到的工具。如今,Qt已被運用於超過70個行業、數千家企業,支持數百萬設備及應用。
快捷編輯器示例展示瞭如何創建一個基本的讀寫層次模型,來與Qt的標準視圖和QKeySequenceEdit類一起使用。
Qt的模型/視圖架構爲視圖提供了一種標準的方式來操作數據源中的信息,使用數據的抽象模型來簡化和標準化訪問數據的方式。快捷編輯器模型將操作表示爲項目樹,並允許視圖通過基於索引的系統訪問此數據。更一般地說,可以使用模型以樹結構的形式表示數據,方法是允許每個項作爲子項表的父項。
在上文中(點擊這裏回顧>>),我們爲大家介紹了快捷編輯器的設計理念及結構等,本文將繼續介紹一些具體的實現類。
ShortcutEditorModel類實現
構造函數接受一個參數,其中包含模型將與視圖和委託共享的數據:
ShortcutEditorModel::ShortcutEditorModel(QObject *parent) : QAbstractItemModel(parent) { m_rootItem = new ShortcutEditorModelItem({tr("Name"), tr("Shortcut")}); }
由構造函數來爲模型創建根項,爲方便起見,此項僅包含垂直標題數據。我們還使用它來引用包含模型數據的內部數據結構,並使用它來表示模型中頂級項的假想父項。
模型的內部數據結構由setupModelData()函數填充,我們將在本文末尾單獨研究這個函數。
析構函數確保在模型被銷燬時刪除根項及其所有子類:
ShortcutEditorModel::~ShortcutEditorModel() { delete m_rootItem; }
由於在構造和設置模型之後我們不能向模型中添加數據,因此這簡化了管理內部項目樹的方式。
模型必須實現index()函數來爲視圖和委託提供索引,以便在訪問數據時使用。當其他組件被它們的行號、列號以及它們的父模型索引引用時,爲它們創建索引。如果將無效的模型索引指定爲父索引,則由模型返回與模型中的頂級項對應的索引。
當提供模型索引時,我們首先檢查它是否有效。如果不是假定引用的是頂級項;否則我們使用模型索引的internalPointer() 函數從模型索引中獲取數據指針,並使用它來引用TreeItem對象。注意我們構造的所有模型索引都將包含一個指向現有TreeItem的指針,因此可以保證接收到的任何有效模型索引都將包含一個有效的數據指針。
void ShortcutEditorModel::setActions() { beginResetModel(); setupModelData(m_rootItem); endResetModel(); }
由於此函數的行和列參數引用相應父項的子項,因此我們使用TreeItem::child()函數獲得該項,createIndex()函數用於創建要返回的模型索引。我們指定行號和列號,以及指向項本身的指針,稍後可以使用模型索引來獲取項目的數據。
TreeItem對象的定義方式使得parent()函數的編寫變得簡單:
QModelIndex ShortcutEditorModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); ShortcutEditorModelItem *parentItem; if (!parent.isValid()) parentItem = m_rootItem; else parentItem = static_cast<ShortcutEditorModelItem*>(parent.internalPointer()); ShortcutEditorModelItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); return QModelIndex(); }
我們只需要確保永遠不會返回與根項對應的模型索引,爲了與index()函數的實現方式保持一致,我們爲模型中任何頂級項的父項返回一個無效的模型索引。
當創建要返回的模型索引時,我們必須在父項中指定父項的行號和列號。我們可以很容易地使用TreeItem::row()函數發現行號,但是我們遵循指定0作爲父列號的約定。模型索引是用createIndex()創建的,方法與index()函數相同。
rowCount()函數只是返回對應於給定模型索引的TreeItem的子條目的數量,或者如果指定了無效索引則返回頂級條目的數量:
QModelIndex ShortcutEditorModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); ShortcutEditorModelItem *childItem = static_cast<ShortcutEditorModelItem*>(index.internalPointer()); ShortcutEditorModelItem *parentItem = childItem->parentItem(); if (parentItem == m_rootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); }
由於每個項目都管理自己的列數據,因此columnCount()函數必須調用項目自己的columnCount()函數來確定給定模型索引有多少列。與rowCount()函數一樣,如果指定了無效的模型索引,則返回的列數將從根項確定:
int ShortcutEditorModel::rowCount(const QModelIndex &parent) const { ShortcutEditorModelItem *parentItem; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = m_rootItem; else parentItem = static_cast<ShortcutEditorModelItem*>(parent.internalPointer()); return parentItem->childCount(); }
數據通過Data()從模型中獲得,由於項目管理它自己的列,我們需要使用列號來使用TreeItem::data()函數檢索數據:
int ShortcutEditorModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return static_cast<ShortcutEditorModelItem*>(parent.internalPointer())->columnCount(); return m_rootItem->columnCount(); }
注意,在這個實現中我們只支持DisplayRole,並且還爲無效的模型索引返回無效的QVariant對象。
我們使用flags()函數來確保視圖知道模型是隻讀的:
QVariant ShortcutEditorModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant(); ShortcutEditorModelItem *item = static_cast<ShortcutEditorModelItem*>(index.internalPointer()); return item->data(index.column()); }
headerData()函數返回我們方便地存儲在根項中的數據:
Qt::ItemFlags ShortcutEditorModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags modelFlags = QAbstractItemModel::flags(index); if (index.column() == static_cast<int>(Column::Shortcut)) modelFlags |= Qt::ItemIsEditable; return modelFlags; }
這些信息可以以不同的方式提供:在構造函數中指定,或者硬編碼到headerData()函數中。
QVariant ShortcutEditorModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { return m_rootItem->data(section); } return QVariant(); }
TODO
void ShortcutEditorModel::setupModelData(ShortcutEditorModelItem *parent) { ActionsMap actionsMap; Application *application = static_cast<Application *>(QCoreApplication::instance()); ActionManager *actionManager = application->actionManager(); const QList<QAction *> registeredActions = actionManager->registeredActions(); for (QAction *action : registeredActions) { QString context = actionManager->contextForAction(action); QString category = actionManager->categoryForAction(action); actionsMap[context][category].append(action); } QAction *nullAction = nullptr; const QString contextIdPrefix = "root"; // Go through each context, one context - many categories each iteration for (const auto &contextLevel : actionsMap.keys()) { ShortcutEditorModelItem *contextLevelItem = new ShortcutEditorModelItem({contextLevel, QVariant::fromValue(nullAction)}, parent); parent->appendChild(contextLevelItem); // Go through each category, one category - many actions each iteration for (const auto &categoryLevel : actionsMap[contextLevel].keys()) { ShortcutEditorModelItem *categoryLevelItem = new ShortcutEditorModelItem({categoryLevel, QVariant::fromValue(nullAction)}, contextLevelItem); contextLevelItem->appendChild(categoryLevelItem); for (QAction *action : actionsMap[contextLevel][categoryLevel]) { QString name = action->text(); if (name.isEmpty() || !action) continue; ShortcutEditorModelItem *actionLevelItem = new ShortcutEditorModelItem({name, QVariant::fromValue(reinterpret_cast<void *>(action))}, categoryLevelItem); categoryLevelItem->appendChild(actionLevelItem); } } } }
TODO
bool ShortcutEditorModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole && index.column() == static_cast<int>(Column::Shortcut)) { QString keySequenceString = value.toString(); ShortcutEditorModelItem *item = static_cast<ShortcutEditorModelItem *>(index.internalPointer()); QAction *itemAction = item->action(); if (itemAction) { if (keySequenceString == itemAction->shortcut().toString(QKeySequence::NativeText)) return true; itemAction->setShortcut(keySequenceString); } Q_EMIT dataChanged(index, index); if (keySequenceString.isEmpty()) return true; } return QAbstractItemModel::setData(index, value, role); }
TODO
在模型中設置數據
我們使用setupModelData()函數在模型中設置初始數據,該函數檢索已註冊的操作文本並創建記錄數據和整體模型結構的項目對象。當然,這個函數的工作方式是非常特定於這個模型的。
爲了確保模型正確工作,只需要創建具有正確數據和父項的ShortcutEditorModelItem實例。