Qt?是目前最先進、最完整的跨平臺C++開發工具。它不僅完全實現了一次編寫,所有平臺無差別運行,更提供了幾乎所有開發過程中需要用到的工具。如今,Qt已被運用于超過70個行業、數千家企業,支持數百萬設備及應用。
快捷編輯器示例展示了如何創建一個基本的讀寫層次模型,來與Qt的標準視圖和QKeySequenceEdit類一起使用。
點擊獲取Qt Widget組件下載(Q技術交流:166830288)
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實例。
Qt Widget組件推薦
- QtitanRibbon?- Ribbon UI組件:是一款遵循Microsoft Ribbon UI Paradigm for Qt技術的Ribbon UI組件,QtitanRibbon致力于為Windows、Linux和Mac OS X提供功能完整的Ribbon組件。
- QtitanChart?- Qt類圖表組件:是一個C ++庫,代表一組控件,這些控件使您可以快速地為應用程序提供漂亮而豐富的圖表。
- QtitanDataGrid?- Qt網格組件:提供了一套完整的標準 QTableView 函數和傳統組件無法實現的獨特功能。使您能夠將不同來源的各類數據加載到一個快速、靈活且功能強大的可編輯網格中,支持排序、分組、報告、創建帶狀列、拖放按鈕和許多其他方便的功能。
- QtitanDocking:允許您像 Visual Studio 一樣為您的偉大應用程序配備可停靠面板和可停靠工具欄。黑色、白色、藍色調色板完全支持 Visual Studio 2019 主題!